ログをJSON形式で出力する

このページではログをJSON形式で出力する方法について解説します。設定ファイルにJSONを使用する方法についてはこちらを参照してください。

JSON Lines形式でのログ出力

ログの形式は伝統的なスペース区切りからLTSV等さまざまなフォーマットがありますが、2022年時点Webの界隈では1行1行がJSONとなっているJSON Lines形式が比較的人気がある形式ではないでしょうか。

予め構造化された形式であるため、標準化しておくと効率的に転送・収集したログの解析、集計等を行うことができます。

python-json-logger

やり方はいくつかあり、Pythonのフォーマッタでもある程度はなんとかなるかもしれませんが、個人的にはpython-json-loggerというライブラリの使用をおすすめします。

python-json-logger

以下のコマンドでインストールすることができます。

pip install python-json-logger

使用方法についてはフォーマッタとしてjsonlogger.JsonFormatterを生成し指定するだけです。jsonlogger.JsonFormatterは組み込みのlogging.Formatterを継承しており、fmt等そのまま引数で設定可能です。

以下、出力サンプルです。デフォルトではmessageがASCIIエンコードされてしまうためjson_ensure_asciiをFalseに設定します。

import logging
from pythonjsonlogger import jsonlogger

# ロガーを生成
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)  # 出力レベル指定

# ハンドラを生成
h = logging.StreamHandler()
h.setLevel(logging.DEBUG)  # 出力レベルを設定

# フォーマッタを生成
json_fmt = jsonlogger.JsonFormatter(fmt='%(asctime)s %(levelname)s %(name)s %(message)s', json_ensure_ascii=False)

# ハンドラにフォーマッターを設定
h.setFormatter(json_fmt)

# ロガーにハンドラを設定
logger.addHandler(h)

# ログ出力
logger.info("ログを出力")

実行すると以下のようにJSON Lines形式で出力されます。

{"asctime": "2022-02-14 21:53:14,147", "levelname": "INFO", "name": "__main__", "message": "ログを出力"}

JsonFormatter継承してJSONのキーをカスタマイズすることもできます。以下、PyPIのドキュメントからの引用です。

class CustomJsonFormatter(jsonlogger.JsonFormatter):
    def add_fields(self, log_record, record, message_dict):
        super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
        if not log_record.get('timestamp'):
            # this doesn't use record.created, so it is slightly off
            now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
            log_record['timestamp'] = now
        if log_record.get('level'):
            log_record['level'] = log_record['level'].upper()
        else:
            log_record['level'] = record.levelname

Djangoでの設定例

おそらくニーズが高いであろうDjangoでの設定例を補足で解説します。文字列でフォーマッタクラス"pythonjsonlogger.jsonlogger.JsonFormatter"を指定します。その他の引数としてjson_ensure_asciiとには出力させたい項目をfmtに列挙して指定します。settings.pyのLOGGINGで設定する場合、以下のようになります。

formatters = {
   "json_fmt": {"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
                  "json_ensure_ascii": False,
                  "fmt": "%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s"}
}
LOGGING = {
   'version': 1,
   'disable_existing_loggers': False,
   'formatters': formatters,
   'handlers': {
       'my_handler': {
           'level': 'INFO',
           'class': 'logging.StreamHandler',
           'formatter': 'json_fmt',
       }
   },
   # loggerの設定
   'loggers': {
       # 画面アプリケーションログ
       'mylog': {
           'handlers': ['my_handler'],
           'level': 'INFO',
       }
   }
}

ログ出力箇所

import logging
logger = logging.getLogger("mylog")
logger.info("メッセージ")

出力結果

{"asctime": "2022-02-15 23:27:21,250", "levelname": "INFO", "module": "urls", "process": 9568, "thread": 9824, "message": "メッセージ"}