これは、エラー・ロギング Advent Calendar 2020 - Adventar25日目のブログです。
最終日はTkSugarに続き、わたしが公開しているモジュール、LogReporterについてお話ししようかと思います。
何をするモジュール?
このモジュールは、Pythonのロガーを設定することで、出力したログの一部を外部サービスに送信するというモジュールです。
現在はDiscordにのみ対応しており、プログラムの動作中に出力されたWARNING以上のレベルのログを、任意のタイミングでDiscordに送信することができます。
とくに最近になって、開発者の元にエラー情報を送信する という機能は、とくに第三者が使用するアプリでは割と必須ではないかと思います。
第三者環境でどんな問題が起こっているか把握できなくては、バグを検証・修正することはできません。
もちろんバグに遭遇した本人が適切にその内容をフィードバックしてくれれば良いのですが、使用者のITリテラシーが高くなかったり、そもそもフィードバックを送るという習慣がなかったりすると、その内容が正しく開発者にフィードバックされません。
そのためにも、ログをなんらかの方法で開発者にフィードバックするシステムを作っておく必要がある と思うのですが、昨今はWebアプリが一般的になったからか、そのようなモジュールがなかなか見つからない。そんなわけで作ってみたモジュールです。
使用の前に:ウェブフックを作成する
このモジュールを利用するためには、まずDiscordのアカウントと、自分が管理権限を持つサーバが必要です。
管理権限を持つサーバにてサーバ設定>連携サービスから、ウェブフックの作成を行います。
作成を押す新しいウェブフックが表示されますので、名前やアイコンを適当に変更して、ウェブフックURLをコピーします。
これは、こんな感じのURLとなります。
https://discord.com/api/webhooks/数字列/長い英文字列
このアドレスに、規定の書式で作成されたJSONフォーマットのデータを投げることによって、Discordへの通知を実現しています。
動作の流れ
セットアップ
LogReporterモジュールには、Reporterというクラスが存在します。このクラスはシングルトン実装となっており、どこでコンストラクタを呼び出しても常に同じインスタンスが返るようになっています。
まずこのReporterオブジェクトのsetup)()メソッドを呼び出し、初期設定を行います。
import logging from logreporter import Reporter from logreporter.report import DiscordWHReporter from logreporter.formatter import DiscordReportFormatter # 中略 def main(): logger = logging.getLogger("apptitle") Reporter().setup(logger, DiscordWHReporter("ウェブフックのURL"), format=DiscordReportFormatter(fmt="%(asctime)s %(levelname)s %(name)s :%(message)s")) main()
ウェブフックのURLと書かれたところに、上記で準備したウェブフックのURLをそのままペーストします。
引数formatには、DiscordReportFormatter()という独自のクラスを指定しておくと楽です。例外文字列やスタックトレースをDiscord上で読みやすいように加工してくれます。
ログの追加
ログの追加については、特段意識することなく、ただただReporter#setup()
の引数に指定したLogger(上ではlogger)に出力していきます。
初期設定ではWARNING、ERROR、CRITICALレベルのログのみが通知対象です。
実行
実際にログをDiscordに送りたいときは、Reporter#upload_report()
というメソッドを使用します。
このメソッドを利用すると、前にReporter#upload_report()
を実行してから今までのすべてのログが、ウェブフックを利用してサーバに送られます。
Reporter().upload_report()
ログはDiscordの制限である2,000文字を超えないように適切に分割されて送信されます。
ログにメッセージをつける
また、upload_report()には、ひとつだけ任意のメッセージを設定することができます。
たとえばユーザーが複数人いるときなど、どの人から送られたデータかを区別するための符号を入れておくと便利です。
Reporter().upload_report(message=self.uuid)
とはいえいきなり個人名を使ってしまうのは個人情報保護の観点からよろしくないので、たとえばUUIDを生成してそれを使うなどすると、とりあえず「Aさんが送ったログとBさんが送ったログが混ざる」などといった問題は回避できるのではないか?と思います。
ログ出力の一般的な注意
ログ出力に関する一般的な注意ですが…。いくつか。
WARNING以上のログに機密情報が表示されないように気をつける
WARNING以上のログは自動的にすべてサーバに送信されることになりますので、利用者の個人情報や機密につながりうるもの(ファイルパスやユーザーの入力など)は表示しないようにすることが重要です。
いざLogReporterを設置する段階になってからこの辺を考えると必ず漏れが出ますので、プロジェクトの開始当初からログの出力を検証するようにしておくと良いでしょう。
ログが出力されるDiscordサーバーの設定について
Discordでは、基本的に見知らぬ人でもサーバの招待アドレスさえ踏めば参加できてしまう仕様になっています。
分かった上で運用しているのであればいいですが、そうでない場合はログが誰でも読める場所に投稿されないような配慮が必要になります。
たとえば投稿されるチャンネルにカギを掛けておく。管理者以外の人がチャンネルを覗けないようにしておくなどといった設定は必要になるでしょう。
ログ送信に関する同意を取っておく
また基本にはなりますが、プログラムを実行するときに、ログの送信などアプリケーションの品質向上に協力するかどうか などといった同意ダイアログがどこかしらで必要になると思います。
設定ダイアログでオンにできますなどだとだれもオンにしないので、なるべく最初期の段階でログを送信しても良いかどうかを確認するようにすると良いでしょう。
なお、そんなときにも便利な設定があって、Reporter#enabled
プロパティをFalseに設定すると、Reporterクラスは機能しなくなります。
ログ出力の設定とセットにしておくとよいのではないでしょうか。
ログ出力の大切さと、それをちゃんと使うことの大切さ
ログ出力は、問題発生時の原因究明に役立ちます。
なので適切にログを出力することは大事なのですが、それ以上に見るべき人にログを届けることも重要です。
このモジュールがアプリのログ解析の一助となれば幸いです。