aoishiの備忘録

備忘録

Pythonでloggingモジュール使ってライブラリ内でログ出力する時のまとめ

はじめに

Pythonでloggingモジュールを使う際の備忘録です。

下記の要求を実現することを目的としています。

  • ライブラリ化したいコードがあり、デバッグ目的などでログを出力させたい。
  • ライブラリで発生した例外のトレースも同じログに出力させたい。
  • ライブラリを利用するスクリプトがあり、ログの出力先やログレベルなどをカスタマイズしたい。

方法

ライブラリ側のロギング設定

logging.getLogger()でモジュール名(__name__)を指定して名前付きでロガーを作成します。

ロガーは階層構造になっており、ここで作成したロガーはルートロガーの子ロガーとなります。

ルートロガーはlogging.getLoggerで名前を指定しない場合に取得できますが、 ライブラリ自身はルートロガーは使用せずに、モジュール毎に名前付きで作成されたロガーでログ出力するのが良いようです。

ライブラリ側で作成したロガーにはNullHandlerという何もしない(ログ出力しない)ハンドラーを設定します。

NullHandlerを設定されたロガーは、イベントが記録(ログ出力)されても何もしませんが、 ロガーのpropagate変数がTrueの場合には、イベントが上位ロガーに伝播されるため、そこで設定されたハンドラーによりイベントが処理されます。

ライブラリ利用側ではこの仕組みを利用し、ここで作成したロガーかより上位ロガーに対してロギング設定していきます。

from logging import getLogger, DEBUG, NullHandler
logger = getLogger(__name__)
logger.addHandler(NullHandler())
logger.setLevel(DEBUG)

# ロガーで記録されたイベント(ログ出力)が上位ロガーに伝播されるかどうか。
# True(デフォルト値)の場合、親ロガーを通じルートロガーまで伝播し、上位ロガーのハンドラー設定に応じてログ出力される。
# ロガーとその上位ロガーそれぞれでハンドラーが設定されていた場合は重複してログ出力される。
logger.propagate = True

# ロガーを使用してログ出力する(NullHandlerのためこのままでは何も出力されない)
logger.debug("Hello World")

# 例外のトレースをロガーに出力する(NullHandlerのためこのままでは何も出力されない)
try:
    # ここで例外を発生させる
except Exception as e:
    logger.warning(e, exc_info=True)

ライブラリ利用側のロギング設定

例として、ファイルにログ出力して日時でローテーションするロギング設定を記述します。

先にライブラリ側のロギング設定でも説明しましたが、ライブラリ内で作成したロガーに対して記録されたイベントは上位ロガーを通じてルートロガーにまで伝播されます。

ここでは、簡単のためにlogging.getLogger()で名前を指定せずルートロガーを取得してロギング設定しています。

具体的なロギング設定については参考文献を参照してください。

from logging.handlers import TimedRotatingFileHandler
import logging

# ルートロガーを取得
logger = logging.getLogger()

# フォーマッターを作成
formatter = logging.Formatter('%(asctime)s %(name)s %(funcName)s [%(levelname)s]: %(message)s')

# ハンドラーを作成しフォーマッターを設定
handler = TimedRotatingFileHandler(
    filename="path/to/log.log",
    when="D",
    interval=1,
    backupCount=31,
)
handler.setFormatter(formatter)

# ロガーにハンドラーを設定、イベント捕捉のためのレベルを設定
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

まとめ

  • ライブラリ側では、何もしないロガーを作成してイベントを記録する。
  • ライブラリ利用側では、ルートロガーを取得してロギング設定する。
  • ライブラリ側およびライブラリ利用側に関らず、ルートロガーにイベントを記録するのは避ける。

参考

ライブラリ側とその利用側のloggingの使い分け方

Logging HOWTO — Python 3.6.3 ドキュメント

ログ出力のための print と import logging はやめてほしい - Qiita

Python標準のloggingでログをJSON形式で出力する - Qiita

例外処理時にエラートレースを出力する方法

29.9. traceback — スタックトレースの表示または取得 — Python 3.6.3 ドキュメント

loggingモジュールで例外処理時にトレース出力する方法

loggingで例外情報を出力する | Python Snippets

loggingモジュールのフォーマット文字列

16.6. logging — Python 用ロギング機能 — Python 3.6.3 ドキュメント