このページではログを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": "メッセージ"}
