Pythonのログ記録


アプリケーションがより複雑になるので、良いログを持つことは、デバッグ時だけでなく、アプリケーションの問題/パフォーマンスに対する洞察を提供するのに非常に役に立つことができます.ロギングも有名なの要因の一つですThe twelve-factor app .
Python標準ライブラリはlogging 基本的なロギング機能のほとんどを提供するモジュール.正しく設定することにより、ログメッセージは、ログおよびログがどこで実行されるかについての有用な情報をもたらすことができます.
利点にもかかわらず、ロギングモジュールはしばしば、それが適切に設定するのに若干の時間がかかるので、見落とされます.atの公式dochttps://docs.python.org/3/library/logging.html , 完全ですが、ベストプラクティスを与えたり、いくつかのログ記録の驚きを強調表示しません.
このPythonロギングチュートリアルは、ロギングモジュールの完全なドキュメントではなく、いくつかのログの概念を紹介する入門ガイドである.ポストはベストプラクティスで終了します.
ポスト内のすべてのコードスニペットは既にログモジュールをインポートしていると仮定します.
import logging

Pythonログの概念


このセクションでは、ロギングモジュールでよく遭遇する概念の概要を示します.

レベル


ログレベルはログが与えられる重要度に対応するerror それから、ログはより緊急でなければなりませんwarn ログ、一方でdebug アプリケーションのデバッグ時にのみログを有効にする必要があります.
Pythonには6つのログレベルがあります.各レベルは、ログの重大度を示す整数に関連付けられています.NOTSET=0, DEBUG=10, INFO=20, WARN=30, ERROR=40, CRITICAL=50.

フォーマッタ


ログformatter それに文脈情報を加えることによって、ログメッセージを豊かにしてください.ログが送られた時、(Pythonファイル、行番号、メソッドなど)、およびスレッドやプロセスIDなどの追加のコンテキストを知るのに便利です.
例えば、ログhello world を返します.
"%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s"
となる2018-02-07 19:47:41,864 - a.b.c - WARNING - <module>:1 - hello world

ハンドラ


ログhandler ログを効果的に書き込む/表示するコンポーネントです.ログをコンソール経由で表示することができますStreamHandler , 経由でファイルに書き込むFileHandler , またはもメールを介して送信するSMTPHandler , など
各ログハンドラには2つの重要なフィールドがあります.
  • ログにコンテキスト情報を追加するフォーマッタ.
  • レベルが下位であるログをフィルタリングするログレベル.たとえば、infoレベルのログハンドラは、デバッグログを処理しません.
  • 標準ライブラリは、一般的なユースケースに十分なハンドラを提供します.https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers . 最も一般的なものはStreamHandler and FileHandler :
    console_handler = logging.StreamHandler()
    file_handler = logging.FileHandler("filename")
    

    ロガー


    ロガーは、我々は、最も複雑なコンセプトであるほとんどのと対話するオブジェクトです.新しいロガーは次のようにして入手できます.
    toto_logger = logging.getLogger("toto")
    
    ロガーには三つの主要なフィールドがあります.
  • Propagate : ログがロガーの親に伝播されるかどうかを調べます.デフォルトではTrue .
  • Level : ハンドラーレベルのように、ロガーレベルは、より重要なログを除外するために使用されます.ログハンドラとは異なり、レベルは子ロガーでのみチェックされます.ログが親に伝播されると、レベルはチェックされません.これはむしろ直感的な行動です.
  • Handlers : ロガーに到着するときにログが送られるハンドラのリスト.これにより、柔軟なログ処理を可能にします.たとえば、すべてのデバッグログをログ出力するファイルログハンドラと、重要なログにのみ使用される電子メールログハンドラを持つことができます.この点に関して、ロガーハンドラ関係は出版者消費者1に類似しています:ログがloggerレベルチェックを通過するならば、ログはすべてのハンドラに放送されます.

  • ロガーは名前によってユニークです.toto が生成された、その結果としての呼び出しlogging.getLogger("toto") は同じオブジェクトを返します:
    assert id(logging.getLogger("toto")) == id(logging.getLogger("toto"))
    
    あなたが推測したかもしれないように、ロガーには階層があります.階層の上にroot ロガーは、logging.root . このロガーは、メソッドがlogging 例えばモジュールです.logging.debug() . デフォルトでは、ルートログレベルはWARN , したがって、低レベルですべてのログ、例えばlogging.info("info") , は無視される.
    ルートロガーのもう一つの具体性は、デフォルトのハンドラーがWarnより大きいレベルでログが記録される最初に作成されるということです.ルートロガーを直接、あるいは間接的にメソッドを通して使用するlogging.debug() は一般に推奨されない.
    デフォルトでは、新しいロガーが作成されると、その親はルートロガーに設定されます.
    lab = logging.getLogger("a.b")
    assert lab.parent == logging.root # lab's parent is indeed the root logger
    
    しかし、ロガーはドット表記を使用しますa.b ロガーの子になりますa . しかし、これはLoggera が作成されていなければab 親はまだroot ロガー.
    la = logging.getLogger("a")
    assert lab.parent == la # lab's parent is now la instead of root
    
    ロガーがレベルチェック(例えばログレベルがロガーレベルより低いならば、ログが無視される)によって通過するべきであるかどうかを決定するとき、それは実際のレベルの代わりにその有効なレベルを使用します.logger.level . レベルがそうでないならば、有効なレベルはロガーレベルと同じですNOTSET , つまり、デバッグからデバッグまでのすべての値しかし、ロガーレベルがNOTSET , 次に、無効なレベルを持つ最初の祖先レベルが有効レベルになります.
    デフォルトでは、新しいロガーはNOTSET ルートロガーとしてWARN レベル、ロガーの効果的なレベルが警告されます.新しいロガーが何らかのハンドラを接続していても、ログレベルが警告を超えない限り、これらのハンドラは呼び出されません.
    toto_logger = logging.getLogger("toto")
    assert toto_logger.level == logging.NOTSET # new logger has NOTSET level
    assert toto_logger.getEffectiveLevel() == logging.WARN # and its effective level is the root logger level, i.e. WARN
    
    # attach a console handler to toto_logger
    console_handler = logging.StreamHandler()
    toto_logger.addHandler(console_handler)
    toto_logger.debug("debug") # nothing is displayed as the log level DEBUG is smaller than toto effective level
    toto_logger.setLevel(logging.DEBUG)
    toto_logger.debug("debug message") # now you should see "debug message" on screen
    
    デフォルトではlogger ログレベルがloggerレベルより低い場合、ログは無視されます.

    Pythonログのベストプラクティス


    ログモジュールは本当に便利ですが、それは最高のPython開発者のための頭痛の長い時間を引き起こす可能性がありますいくつかの風変わりが含まれています.私の意見では、このモジュールを使用するための最良のプラクティスです
  • ルートロガーを構成しますが、決してコードのような関数を呼び出すことはありませんlogging.info() , これは、シーンの背後にルートロガーを呼び出します.rootロガーの設定は、使用しているライブラリからエラーメッセージをキャッチしたい場合に便利です.
  • ログを使用するには、新しいロガーをlogging.getLogger(logger name) . 通常使用__name__ 個々のモジュールのロガー名または全体のアプリケーション/libの静的な名前として、それは一貫している限り、何かを使用することができます.より多くのハンドラを追加するには、通常、ロガーを返すメソッドがありますhttps://gist.github.com/nguyenkims/e92df0f8bd49973f0c94bddf36ed7fd0 ).
  • import logging
    import sys
    from logging.handlers import TimedRotatingFileHandler
    FORMATTER = logging.Formatter("%(asctime)s — %(name)s — %(levelname)s — %(message)s")
    LOG_FILE = "my_app.log"
    
    def get_console_handler():
       console_handler = logging.StreamHandler(sys.stdout)
       console_handler.setFormatter(FORMATTER)
       return console_handler
    
    def get_file_handler():
       file_handler = TimedRotatingFileHandler(LOG_FILE, when='midnight')
       file_handler.setFormatter(FORMATTER)
       return file_handler
    
    def get_logger(logger_name):
       logger = logging.getLogger(logger_name)
       logger.setLevel(logging.DEBUG) # better to have too much log than not enough
       logger.addHandler(get_console_handler())
       logger.addHandler(get_file_handler())
       # with this pattern, it's rarely necessary to propagate the error up to parent
       logger.propagate = False
       return logger
    
    新しいロガーを作成し、それを使うことができます.
    my_logger = get_logger("my module name")
    my_logger.debug("a debug message")
    
  • 用途RotatingFileHandler などのクラスTimedRotatingFileHandler 代わりに例で使用するFileHandler , ファイルがサイズ制限に達したとき、自動的にあなたのためにファイルを回転させるか、毎日それをしてください.
  • 自動的にあなたのためにエラーログをキャッチする歩哨、エアブレーキ、レイガンなどのようなツールを使用します.これは特にログが非常に冗長であり、エラーログが簡単に失われることができるWebアプリケーションのコンテキストで有用です.これらのツールを使用するもう1つの利点は、エラーが発生したユーザーの原因となるURLを知ることができるように、エラーの変数値の詳細を取得することです.
  • 確実にログを正しく動作させることは、将来的に数え切れないほどのデバッグ時間を節約することができるので、新しいプロジェクトを設定するときに最初にすべきことの一つであるべきです.