このページではログをJSON形式で出力する方法について解説します。設定ファイルにJSONを使用する方法についてはこちらを参照してください。
JSON Lines形式でのログ出力
ログの形式は伝統的なスペース区切りからLTSV等さまざまなフォーマットがありますが、2022年時点Webの界隈では1行1行がJSONとなっているJSON Lines形式が比較的人気がある形式ではないでしょうか。
予め構造化された形式であるため、標準化しておくと効率的に転送・収集したログの解析、集計等を行うことができます。
python-json-logger
やり方はいくつかあり、Pythonのフォーマッタでもある程度はなんとかなるかもしれませんが、個人的には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": "メッセージ"}