16
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Python のログを JSON で出力する

Last updated at Posted at 2022-01-31

Python で書いたサーバのエラーログを CloudWatch Logs に吐き出していたところ、Traceback の改行文字で分割されていてエラー内容が非常に追いづらいという体験をした。

どうやら CloudWatch Logs は JSON でログを吐けばよしなにパースしてくれるらしく、本番環境のログは JSON で吐き出したほうが良さそうということで、Python のログを Traceback つきで JSON 出力するようにしてみた。

初期設定

from os import getenv
import logging
import json


class JSONFormatter(logging.Formatter):
    """ログを JSON で出力するフォーマッタ"""

    def format(self, record: logging.LogRecord) -> str:
        try:
            data = vars(record)
            exc_info = data.pop("exc_info")
            if exc_info:
                data["traceback"] = self.formatException(exc_info).splitlines()
            return json.dumps(data)
        except:
            return super().format(record)


log_handler = logging.StreamHandler()
if getenv("LOG_FORMAT", "default").lower() == "json":
    log_handler.setFormatter(JSONFormatter())
logging.basicConfig(handlers=[log_handler], level=getenv("LOG_LEVEL", logging.WARNING))
  • 環境変数で設定を切り替えれるようにしているが、そのへんは好みで
  • Traceback を行ごとに区切っている (splitlines) が、これは CloudWatch Logs で見やすくするためで、区切らなくてもいいとは思う
  • json.dumps が何らかの理由で失敗したときなどにログをロストしないように super().format(record) している

使い方

from logging import getLogger

logger = getLogger(__name__)

try:
    1/0  # 例外を発生させる
except:
    logger.exception("Unexpected error occurred")
  • ルートロガーの設定を変更しているので、 getLogger で新しくロガーを作成した場合でも特に何も設定する必要はない
  • 例外をキャッチした際に logger.exception を使っておけば LogRecord に exc_info が含まれるので、特に何もしなくても Traceback がログ出力されるので便利
    • logger.exception("...")logger.error("...", exc_info=True) と同じ (たぶん) なので、ログレベルは ERROR になる
  • extra に渡した追加データもそのままログに出力される
    • 例: logger.exception("...", extra={"user_id": "..."})

出力されるエラー

※ 本来は改行されていない1行の JSON 文字列が出力されるが、ここでは見やすくするために整形している

{
  "name": "__main__",
  "msg": "Unexpected error occurred",
  "args": [],
  "levelname": "ERROR",
  "levelno": 40,
  "pathname": "main.py",
  "filename": "main.py",
  "module": "main",
  "exc_text": null,
  "stack_info": null,
  "lineno": 8,
  "funcName": "<module>",
  "created": 1643609361.1079879,
  "msecs": 107.98788070678711,
  "relativeCreated": 3.8950443267822266,
  "thread": 4410674688,
  "threadName": "MainThread",
  "processName": "MainProcess",
  "process": 17896,
  "traceback": [
    "Traceback (most recent call last):",
    "  File \"main.py\", line 6, in <module>",
    "    1/0",
    "ZeroDivisionError: division by zero"
  ]
}

参考

16
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?