Python Tips: Python でオリジナルの logging formatter を使いたい

Python でのロギングにオリジナルのフォーマッタを使う方法をご紹介します。

Python に同梱の標準ライブラリ logging についてのお話です。

やるべきことはシンプルで、 logging.Formatter を継承したクラスを作成してそれをフォーマッタとしてセットするだけです。 Python の公式ドキュメントを読めばそのやり方が書かれてはいるのですが、経験の無い人には少しわかりづらいのではないかと思いますので、かんたんな例を使って説明します。

次のサンプルは、ログの名前を大文字に変換して出力するだけのシンプルなフォーマッタ CustomFormatter を定義して使用する例です。

custom_formatter.py:

import logging


class CustomFormatter(logging.Formatter):
    def format(self, record):
        record.name = record.name.upper()
        return super().format(record)


LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {},
    'formatters': {
        'custom_formatter': {
            '()': CustomFormatter,
            'format': '%(asctime)s\t%(name)s\t%(message)s',
        },
    },
    'handlers': {
        'sample': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': '/tmp/python.log',
            'formatter': 'custom_formatter',
        },
    },
    'loggers': {
        'special': {
            'level': 'INFO',
            'handlers': ['sample'],
            'propagate': True,
        },
    },
}


logging.config.dictConfig(LOGGING)
logger = logging.getLogger('special')

logger.info('Hello')

このファイルを実行すると、ファイル /tmp/python.log に次のようなログが出力されます。

2020-09-09 18:33:26,144 SPECIAL Hello

ログの名前にあたる special が大文字 SPECIAL になって出力され、望んだとおりに CustomFormatter が使えていることが確認できます。

オリジナルのフォーマッターを定義するときは、上のように logging.Formatter を継承しメソッド format() を持つクラスを作成します。

class CustomFormatter(logging.Formatter):
    def format(self, record):
        ...

この format() の引数 record は 1 件のログレコードを表す logging.LogRecord クラスのオブジェクトで、以下のようなアトリビュートを持っています( Python 3.8 で確認しています。バージョンが異なると変わる可能性があります)。

  • args
  • created
  • exc_info
  • filename
  • funcName
  • levelname
  • levelno
  • lineno
  • module
  • msecs
  • msg
  • name
  • pathname
  • process
  • processName
  • relativeCreated
  • stack_info
  • thread
  • threadName

どんな値が入っているのかはそれぞれの名前からある程度推測できますが、厳密に知りたい場合は公式ドキュメントの logging のページの以下の箇所または logging/__init__.py ファイルを確認するとよいと思います。

LogRecord オブジェクトにはテンプレート文字列 msgargs を挿入した文字列を返すメソッド getMessage() もあります。

メソッド format() が戻り値として返すべき値は 1 件のログレコードを表す文字列です。 ログハンドラがログレコードを出力するときにこの format() が呼ばれます。

logging.Formatter はメソッド format() を持っているので、ちょっとしたカスタマイズであれば super().format(record) でそれを利用できます。 カスタマイズの程度が大きくなると logging.Formatter が提供する format() は使えないので、 record.getMessage() 等を使ってフォーマット済み文字列を独自に生成する必要があります。

幅広い用途に使える汎用のフォーマッタを作りたければ、公式のドキュメントに加えて logging のコードをチェックする必要があります。

たいていのケースは format() を定義するだけで対応できますが、 format() だけでは対応しきれない場合は次のように __init__() メソッドを上書きする必要があります:

import csv
import logging


class CsvFormatter(logging.Formatter):
    """CSV 形式で出力するフォーマッター"""

    def __init__(self, fmt=None, datefmt=None, style='%', validate=True):

        # レコードごとに csv writer と StrnigIO を生成しなくて済むように最初に用意する
        self.output = io.StringIO()
        self.writer = csv.writer(self.output, delimiter='\t')

    def format(self, record):
        ...

ということで、かんたんにですが Python でオリジナルのロギングフォーマッタを使う方法についてでした。

参考: