Django Logging, The Right Way

良いログはデバッグや問題のトラブルシューティングに欠かせません。 ローカルな開発で役に立つだけでなく、実稼働環境では不可欠です。 問題のログをレビューするとき、誰かが「私たちのアプリケーションではロギングが多すぎる」と言うのはまれですが、逆のことを聞くのはよくあることです。

A Crash Course in Python Loggers

すべてのファイルの先頭には、次のようなものがあるはずです:

import logginglogger = logging.getLogger(__name__)

__name__ はモジュールのドット付き Python パスに評価されます、たとえば、myproject/myapp/views.pymyproject.myapp.views を使用することになります。

# A simple string logged at the "warning" levellogger.warning("Your log message is here")# A string with a variable at the "info" levellogger.info("The value of var is %s", var)# Logging the traceback for a caught exceptiontry: function_that_might_raise_index_error()except IndexError: # equivalent to logger.error(msg, exc_info=True) logger.exception("Something bad happened")

Note: Python のロガーは、ロギング関数の引数として変数を渡せば、ログメッセージへの変数の挿入を処理します。 ログが出力される必要がない場合、変数の置換は決して起こらないので、決して使用されないログのための小さなパフォーマンスのヒットを回避するのに役立ちます。

  • debug: 通常の操作には必要ないが、開発時には有用な情報。
  • info: 通常の操作時に有用な情報。
  • error: 重要で迅速な対応が必要な情報。
  • critical: 実際には使用しませんが、error より高い値が必要な場合は、これを使用します

Where to Log

アプリではログがどこに行くかは気にしない方がよいでしょう。 代わりに、コンソール (stdout/stderr) にすべてを記録し、そこから何をするかはサーバーに決定させる必要があります。 通常、これは専用の(そしてログローテートされた)ファイルに置かれ、Systemd ジャーナルや Docker によってキャプチャされ、ElasticSearch などの別のサービスに送られ、あるいはそれらのいくつかの組み合わせで行われます。 ログ ストレージは、デプロイメントに関するものであり、アプリケーションに関するものではありません。 通常、これは関連するデータを含む単なる文字列ですが、サーバーがすでにログにタイムスタンプを追加している場合、おそらく独自の formatter からそれを除外したいことでしょう。 同様に、ログアグリゲータが JSON を受け入れることができる場合、 python-json-logger のようなフォーマッタがより適切かもしれません。

Configuring the Logger

Python でロガーに書き込むことは簡単です。 正しい場所に行くように設定することは、予想以上に困難です。 前の投稿で説明したように、Django のデフォルトのロギングをバイパスすることから始めましょう。

Setting up Sentry

Error reporting services は、特別なサイトでない限り、重要です。 デフォルトでは、捕捉されない例外をキャッチし、問題を通知し (インシデントごとに 1 回のみ)、例外が発生したときのアプリケーションの状態を確認するための素晴らしいインターフェイスを提供します。 このための私のお気に入りのサービスは Sentry.

Sentry をさらに一歩進めて、warning 以上のログ メッセージをすべてこのサービスに送信することができます。 そうしないと、ログファイルの海の中に紛れ込んでしまい、実際にはほとんど見直されないからです。 これを行うには、任意の Python モジュールから送信されるすべてのログのためのキャッチオールとして機能する “root” ロガーを追加します。 Django の設定では、以下のようになります。

import logging.configLOGGING_CONFIG = Nonelogging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'console': { # exact format is not important, this is the minimum information 'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'console', }, # Add Handler for Sentry for `warning` and above 'sentry': { 'level': 'WARNING', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', }, }, 'loggers': { # root logger '': { 'level': 'WARNING', 'handlers': , }, },})

Logging From Your Application

サードパーティの依存関係からの警告やエラーについてだけ知りたいかもしれませんが、一般的にはアプリケーションコードについてもっと深い洞察を得たいものでしょう。 理想的には、コードはすべて単一のネームスペースの下に存在し、loggers に 1 回追加するだけでキャプチャできます。 あなたのプロジェクトが名前空間 myproject を使用していると仮定すると、上記のコードを基に次のように追加します。 新しいコードをコミットしてそれをデプロイしなければならないのは、過剰な作業のように感じられます。 これは環境変数の素晴らしい使用例です。 前のスタンザを次のように修正できます:

import osLOGLEVEL = os.environ.get('LOGLEVEL', 'info').upper()logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, },})

これで、アプリケーションのロギングはデフォルトで info になりますが、環境変数 LOGLEVEL=debug を設定することで簡単に一時的に増加させることができます。 また、ログの保存が問題でなければ、常に debug レベルでログを記録することを検討してください。 これらのログは、単純な grep や Kibana などのログ視覚化ツールで簡単にフィルターできます。

ノイズを取り除く

一度、ログを設定して実行すると、いくつかのモジュールは、本当に気にしない情報をログし、余計なノイズを生成するのに役立つことがあります (私は newrelic に注目しています)。 このようなモジュールには、別のロガーを追加して、それらを調整することができます。

logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, # Don't send this module's logs to Sentry 'noisy_module': { 'level':'ERROR', 'handlers': , 'propagate': False, }, },})

もし、コンソールにさえもうるさいと感じたら、それらを完全に削除することができます。 Django の設定を上書きすることで、これを失いますが、元に戻すのは簡単です:

from django.utils.log import DEFAULT_LOGGINGlogging.config.dictConfig({ # ... 'formatters': { # ... 'django.server': DEFAULT_LOGGING, }, 'handlers': { # ... 'django.server': DEFAULT_LOGGING, }, 'loggers': { # ... 'django.server': DEFAULT_LOGGING, },})

Leave a Reply