処理追跡のため全てのログ出力に処理固有の値を出したい
目的
最初に決まる処理固有の値(request_idやbatch_id等)を複数のモジュールや関数でログ出力したい
バッドパターン
独自のCustomLoggerクラスを実装し、 request_id, batch_id を初期化時に持たせた、インスタンスを利用先へ渡すことで、利用先でその値をログ出力する。
デメリット
テストコードが必要になる
関数引数が増え、複雑度が増し、コードの理解が難しくなる
引数が増えた関数のUnitTestを書き換える必要があり、テストが複雑化する
そもそもフレームワーク等の内部に渡すことはできないため使えない
code:log.py
class CustomLogger:
def __init__(self, batch_id):
self.batch_id # ここで固有の値を保持
def info(self, message=None, *args, **kwargs):
"""infoログ出力"""
self._write_log("info", message, *args, **kwargs)
# debug, warning, error, exception も同様に実装
def _write_log(self, log_level, message=None, *args, **kwargs):
"""ログ出力共通処理"""
getattr(self.logger, log_level)(
message=message,
batch_id=batch_id, このロガーインスタンスは固有の値をログ出力する
*args, **kwargs)
ベストプラクティス
structlogの structlog.contextvars.bind_contextvars で request_id, batch_id, をbindする。 structlog.configureで structlog.contextvars.merge_contextvars をprocessorsに指定することで、bind_contextvarsした値はコンテキスト(スレッドや非同期)内で共有され、どのloggerインスタンスにも含まれる。
メリット
呼び出し先のモジュール、関数では通常のstructlogの使い方をすればよい
code:main.py
logger = structlog.get_logger()
def main(...):
structlog.contextvars.bind_contextvars(batch_id=1234)
...
try:
client = SomeClient() # logger渡しは不要
# SomeClient側ではstructlog.get_logger()で取得したloggerを使うだけ
参考
タグ