https://a.storyblok.com/f/270183/42525/f13e61dcd0/python_error-alert_1200x600.png

Pythonエラー警告ツールの構築

最終更新日 March 30, 2021

所要時間:12 分

どんなに品質とテストに気を配っていても、ソフトウェアは、ある時点で、ほぼ確実にうまくいかなくなる。その結果、アプリケーションの健全性を追跡するための監視ログは不可欠です。

確かに、アプリケーション・ログを監視するサービスやオープンソース・プロジェクトは複数ある。しかし私の経験では、それらはたいてい高価で、統合するのに時間がかかったり、ほとんど使わない機能で肥大化していたりする。派手な監視を必要としない小さなプロジェクトをデプロイしているとき、コードに何か問題が発生したときにシンプルなアラートを受け取れるPythonネイティブのソリューションがあればいいのにと思うことがある。

このチュートリアルの目的は、まさにこのニーズを満たすことです。シンプルで柔軟な Python エラーアラートツールを作ります。ロギング HTTP ハンドラオブジェクトが非同期に Vonage SMS APIを通して私たちの携帯電話にアラートを送信します。

必要条件

チュートリアルではPython 3.9.1(最新の安定版)を使用しますが、コードはPython 3.6+でも動作するはずです。PythonはLinux、macOS、Windowsで利用可能です。ダウンロードとインストールは 公式ウェブサイト.

また、SMSでエラーアラートを受信するには、Vonageアカウントが必要です。 アカウントを作成するを作成してください。Vonageは新規加入者にAPIをテストするためのクレジット€2.00を無料で提供しています。

Vonage APIキーとシークレットも必要です。 ダッシュボード設定:

Vonage Dashboard

PyPI http-loggingライブラリはログのキャッシュと Vonage API.これにより、メインの Python アプリケーションがアラートメカニズムによって中断されるのを防ぎます。

現地の環境を整える

仮想環境と依存関係

プロジェクト用のディレクトリを作成する:

~$ mkdir vonage-alerts ~$ cd vonage-alerts

仮想環境の構築 仮想環境の作成仮想環境を作成するのは良い練習になる:

~/vonage-alerts$ python3.9 -m venv .env ~/vonage-alerts$ source .env/bin/activate

ウィンドウズ・コンピューターでは、上の最後の行の sourceコマンドに置き換えてください:

~/vonage-alerts$ .venv\Scripts\activate

環境が期待通りに機能していることを確認する:

(.env) ~/vonage-alerts$ python --version Python 3.9.1

では、Pythonの依存ファイルを作成しよう:

(.env) ~/vonage-alerts$ touch requirements.txt

お好みのテキストエディタで開き、以下の行を追加する:

http-logging
vonage

ファイルを閉じて pip installコマンドでインストールします:

(.env) ~/vonage-alerts$ pip install -r requirements.txt

環境変数

カスタム・ロギング・ロジックは、環境変数を通して提供されるいくつかの情報を必要とします。

SMSサービスの認証にはVonage API Keyが必要です。SMSメッセージの送信には電話番号も必要です。

(.env) ~/vonage-alerts$ export VONAGE_API_KEY="abc123" (.env) ~/vonage-alerts$ export VONAGE_API_SECRET="xyz123" (.env) ~/vonage-alerts$ export ALERT_PHONE_NUMBER="+1234567890"

この exportコマンドはLinuxとmacOSで動作するはずだ。Windowsでは、代わりに setを使う。もし PowerShellコンソールを使っている場合は、このコマンドでうまくいくはずだ:

$Env: VONAGE_API_KEY = "abc123"
$Env: VONAGE_API_SECRET = "xyz123"
$Env: export ALERT_PHONE_NUMBER = "+1234567890"

HTTPログ・ハンドラ

前述したように http-loggingライブラリに依存する。

ネイティブの ロギングHTTPハンドラを使うこともできます。しかし、ブロッキングHTTPリクエストを生成し、メインのPythonアプリケーションの実行に悪影響を及ぼす可能性があるため、使用するつもりはありません。

その http-loggingライブラリーは、バックグラウンド・スレッドで静かに実行され、ネットワーク・リクエストの数を減らすために、ローカルのSQLiteデータベースにログをキャッシュすることもできる。このような理由から、ネイティブのHTTPハンドラーよりもずっと邪魔にならないだろう。

このライブラリは Python Logstash Asyncをベースにしていますが、Logstash以外のバックエンドでも動作するように一般化されています(このチュートリアルではVonageを使用します)。詳しくは プロジェクトのドキュメント wiki.

Vonage HTTPトランスポート

最初に必要なのは、カスタムHTTPトランスポート・クラスを作成することだ。これは、Vonage API にログを送信するための命令です。

その前に、カスタム・ロギング・コードを含む新しいPythonファイルを作りましょう:

(.env) ~/vonage-alerts$ touch logging_vonage.py

さあ、Pythonを楽しむ時間だ!

独自のHTTPトランスポート・クラスは、このクラスを継承する。 [http_logging.AsyncHttpTransport](https://github.com/hacktlib/py-async-http-logging/wiki/3.-HTTP-Transport-Class)を継承します。まず、ファイルの先頭で必要なライブラリをインポートし、以下のように新しいクラスを宣言する:

import logging
import os

from vonage import Sms

from http_logging import HttpHost, SupportClass
from http_logging.handler import AsyncHttpHandler
from http_logging.transport import AsyncHttpTransport

class VonageHttpTransport(http_logging.transport.AsyncHttpTransport):
    pass

今のところ、このクラスはオリジナルとまったく同じように動作する。これにカスタム機能を追加してみましょう。このクラスは AsyncHttpTransportsendメソッドを実装しています。最初は リクエストライブラリを使います。私たちの場合は Vonage SDKがあり、HTTPプロトコルの退屈さを解消してくれる。

よし、話はもう十分だ。それでは Vonage SDK新しい sendメソッドを宣言します:

class VonageHttpTransport(AsyncHttpTransport):

    def send(self, events: dict, **kwargs) -> None:
        batches = self._HttpTransport__batches(events)

        sms_logs = ', '.join([
            f"{log['level']['name']}: {log['message']}"
            for batch in batches
            for log in batch
        ])

        sms_message = f'[Python Logger {self.logger_name}] {sms_logs}'

        sms_client = Sms(
            key=self.vonage_api_key,
            secret=self.vonage_api_secret,
        )

        response = sms_client.send_message({
            'from': f'Python Logger {self.logger_name}',
            'to': self.alert_phone_number,
            'text': sms_message,
        })

        if not response['messages'][0]['status'] == 0:
            raise ConnectionError(response["messages"][0].get("error-text"))

この sendメソッドは events引数を取る。 HttpTransport.__batchesメソッドを使ってログのバッチに変換されます。バッチは、基本的なデータポイントをログ文字列に抽出するために処理されます。

各ログ文字列は、ログレベル名(例:「警告」や「エラー」)とログメッセージのみを含む。SMSはショートメッセージサービスの略なので、警告メッセージは短くしたい。私たちの主な目的は、SMSを通して完全なデバッグをサポートすることではなく、警告することです。コンテキストを提供し、開発者がデバッグ・プロセスを開始するのを助けるために、最小限の情報が送信されます。

メソッドを使って連結され、アプリケーションコンテキストに関する情報を提供するために、ロガー名が先頭に付けられます。 string.joinメソッドを使って連結され、 アプリケーションのコンテキストに関する情報を提供するためにロガー名が先頭に 付けられます (これは、複数のプロジェクトがこのアラートツールを使っている場合に 役に立ちます)。

最後に vonage.Smsクライアントを Vonage SDKからクライアントをインスタンス化し、それを使ってSMSメッセージを携帯電話に送信する。レスポンス・ステータスがチェックされ、"OK "でなければ ConnectionError.このエラーを発生させることで、ログ警告メカニズムが後で再試行されることを確認します。 VonageHttpTransportクラスはバックグラウンドスレッドで実行されるからです。

新しい sendメソッドで logger_name, vonage_api_key, vonage_api_secret, alert_phone_number.メソッドをオーバーライドしよう。 __init__メソッドをオーバーライドしましょう:

class VonageHttpTransport(AsyncHttpTransport):

    def __init__(
        self,
        logger_name: str,
        vonage_api_key: str,
        vonage_api_secret: str,
        alert_phone_number: str,
        *args,
        **kwargs,
    ) -> None:
        self.logger_name = logger_name
        self.vonage_api_key = vonage_api_key
        self.vonage_api_secret = vonage_api_secret
        self.alert_phone_number = alert_phone_number
        super().__init__(*args, **kwargs)

新しいHTTPトランスポート・クラスの準備が整いました。しかし、実際のロギング動作に移る前に、まずは実際の ロガーオブジェクトをインスタンス化するロジックを作成する必要があります。 VonageHttpTransportクラスを使用して、実際のロガーオブジェクトをインスタンス化するロジックを作成する必要があります。

ボネージ・ログ・ハンドラー

この VonageHttpTransportクラスは見た目は良いが、それだけでは戦いに行くことはできない。アプリケーションでログを取るために使うことはできないので、もう一歩進めて、戦闘に対応できるようにしよう。

パズルに欠けているのは、実際のHTTPハンドラー・クラスだ。これは http_logging.AsyncHttpHandlerでインスタンス化されたものでなければならない。 VonageHttpTransport.

の中に getLogger関数を作成しよう。 logging_vonage.pyの中に関数を作ってみよう。 logging.getLoggerの振る舞いを真似てみましょう:

def getLogger(name: str) -> logging.Logger:
    pass

ネイティブのPythonの getLogger関数のように、名前文字列を引数にとり logging.Loggerクラスのインスタンスを返します。次に、この関数の機能を順を追って作っていきます。

をインスタンス化することから始めます。 HttpHost.これは VonageHttpTransportHTTP リクエストを Vonage SDK に委譲するためです:

def getLogger(name: str) -> logging.Logger:
    host = HttpHost(name='vonage.com')

次に SupportClassHTTPトランスポートオブジェクトを保持する

support_class = SupportClass(
        http_host=host,
        _transport=VonageHttpTransport(
            http_host=host,
            logger_name=name,
            vonage_api_key=os.environ.get('VONAGE_API_KEY'),
            vonage_api_secret=os.environ.get('VONAGE_API_SECRET'),
            alert_phone_number=os.environ.get('ALERT_PHONE_NUMBER'),
        ),
    )

この SupportClassオブジェクトをインスタンス化するために使用される。 AsyncHttpHandler:

vonage_handler = AsyncHttpHandler(
        http_host=host,
        support_class=support_class,
    )

最後に logging.Loggerオブジェクトをインスタンス化し vonage_handlerをハンドラとして追加し、それを返す:

logger = logging.getLogger(name)
    logger.addHandler(vonage_handler)

    return logger

最終的に getLogger関数は次のようになる:

def getLogger(name: str) -> logging.Logger:
    host = HttpHost(name='vonage.com')

    support_class = SupportClass(
        http_host=host,
        _transport=VonageHttpTransport(
            http_host=host,
            logger_name=name,
            vonage_api_key=os.environ.get('VONAGE_API_KEY'),
            vonage_api_secret=os.environ.get('VONAGE_API_SECRET'),
            alert_phone_number=os.environ.get('ALERT_PHONE_NUMBER'),
        ),
    )

    vonage_handler = AsyncHttpHandler(
        http_host=host,
        support_class=support_class,
    )

    logger = logging.getLogger(name)
    logger.addHandler(vonage_handler)

    return logger

APIキー、シークレット、電話番号は、このチュートリアルの最初に設定した環境変数から取得されていることに注意してください。これにより、このコードを複数のプロジェクトで使いたい場合に柔軟に対応することができる。また、通常良いアイデアとは言えないAPIシークレットのハードコーディングも避けることができる。)

複数のハンドラー

Pythonのロギング機構は非常に強力で、オブジェクトは複数のハンドラで拡張できるほど柔軟です。 logging.Loggerオブジェクトは複数のハンドラで拡張できるほど柔軟です。

上で説明したように VonageHttpTransportクラスは、SMSシステム固有のテキスト長の制限のために、ログに関する最小限の情報しか送信しません。とはいえ、さらなるデバッグが必要なエラーが発生した場合、スタック・トレース全体、どのコードのどの行が失敗したか、正確なタイムスタンプなどの情報を取得したいと思うのは確かです。

このような詳細なログの需要には Logger.addHandlerを使い、Vonage Loggerオブジェクトに

例えば、携帯電話だけでなくコンソールにもログを送信するには、以下のように logging.StreamHandlerを使うことができる:

import logging
import logging_vonage

logger = logging_vonage.getLogger('')
logger.addHandler(logging.StreamHandler())

上記の loggerオブジェクトで記録されたものはコンソールに出力され、 Vonage SMS API を通して私たちの電話に送られます。

A logging.FileHandlerを使うことで、ログをローカルファイルシステムに保存することができる。また、同じ http_logging.AsyncHttpHandlerを使うこともできるが、この場合は Vonage API とは別のバックエンドホストにログを送ることになる。サンプルアプリケーションを使ったテスト さて、ベルとホイッスルを使った実際の動作を見てみよう。というのは冗談で、Vonage SMS APIを使って電話をビープ音で鳴らすところだ。

プロジェクト・ディレクトリに sample_app:

(.env) ~/vonage-alerts$ touch sample_app.py

それを開き、以下の内容を追加する:

import logging
import logging_vonage


logger = logging_vonage.getLogger('sampleapp')

logger.addHandler(logging.StreamHandler())

logger.debug('Debugging...')
logger.warning('You\'ve been warned!')
logger.error('This is a test error')

try:
    1/0
except ArithmeticError as exc:
    logger.exception(exc)

から loggerオブジェクトをインスタンス化していることに注目してほしい。 logging_vonageオブジェクトをインスタンス化していることに注目してください。また logging.StreamHandler()はまた、完全なトレースがコンソールに記録されるようにするために使われています。

コンソールで、このスクリプトを次のように実行する:

(.env) ~/vonage-alerts$ python sample_app.py

以下の出力がコンソールに出力されるはずだ:

You've been warned!
This is a test error
division by zero
Traceback (most recent call last):
  File "/home/vonage-alerts/sample_app.py", line 14
    1/0
ZeroDivisionError: division by zero

うまくいけば、すべての設定(VonageアカウントとAPIキー/シークレット)が正しく行われていれば、まもなく以下のテキストが書かれたSMSメッセージが届くはずです:

[Python Logger sampleapp] WARNING: You've been warned!, ERROR: This is a test error, ERROR: division by zero

Debug message

デバッグメッセージ 'Debugging...'がコンソールに出力されず、SMSメッセージに連結されていないことに注意してください。これは、Pythonロギングライブラリのデフォルトのログレベルが WARNING.であるためです。 DEBUGレベルは WARNINGよりも低いとみなされ、破棄されます。

メッセージを DEBUGメッセージをキャプチャしたい場合は、以下のようにレベルを設定してください:

logger = logging_vonage.getLogger('sampleapp') logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG)

スクリプトをもう一度実行すると sample_app.pyスクリプトをもう一度実行すると、デバッグ・メッセージがコンソールに出力され、SMSメッセージにも連結されているのが見えるはずです。

カスタムハンドラ( loggerカスタムハンドラ(http_logging.AsyncHttpHandler)とカスタムトランスポート(logging_vonage.VonageHttpTransport)クラスに依存しているにもかかわらず、他のPythonの Loggerオブジェクトと同じように振る舞います。このため、今開発したSMSアラートメカニズムをスタック全体や将来のプロジェクトに統合したい場合、現在あるPythonプロジェクトのドロップイン代替品として完全に互換性があります。

まとめ

これだ!シンプルで邪魔にならないPythonアラートツールができました。これは基本的なPython loggingネイティブの機能を拡張し、私たちが慣れ親しんでいるのと同じAPIを使用し、Pythonアプリケーションが実行される場所であればどこでも動作します。財政的には、固定費がなく、維持費も比較的安価です(SMSメッセージの料金のみ)。

http-loggingライブラリは、ログのローカルキャッシュを保持するので、Vonage APIまたは携帯電話キャリアがダウンタイムまたはネットワーク不安定になった場合、ロガーはSMSアラートの送信をしばらくしてから再試行することができます。

シェア:

https://a.storyblok.com/f/270183/400x373/ed2dc20b00/renato-byrro.png
Renato Byrro

Renato is a backend software developer and a father of two amazing kids who won’t let him sleep so that he can enjoy spending nights connecting APIs around.