Pythonでログを出力するloggingモジュールの設定

プログラミング

Python `logging` モジュールの設定:基本から応用まで

Pythonの標準ライブラリである `logging` モジュールは、アプリケーションの動作状況を記録するための強力かつ柔軟なツールです。デバッグ、エラー追跡、パフォーマンス分析など、様々な場面で活用されます。このモジュールを効果的に使いこなすためには、その設定方法を深く理解することが不可欠です。ここでは、`logging` モジュールの設定に関する主要な要素について、基本から応用までを解説します。

ロガー(Logger)

`logging` モジュールにおける中心的な概念が「ロガー」です。ロガーは、メッセージの生成、レベルの判定、ハンドラーへの転送などを担当します。

ロガーの取得

ロガーは `logging.getLogger()` 関数を使って取得します。引数にはロガーの名前を指定します。通常、モジュール名やパッケージ名が使われます。


import logging

logger = logging.getLogger(__name__)

名前の付いたロガーは階層構造を持ちます。例えば、`logging.getLogger(‘my_app.module_a’)` と `logging.getLogger(‘my_app.module_b’)` は、親ロガー `logging.getLogger(‘my_app’)` の下に配置されます。

ロガーレベル

ロガーにはレベルが設定されており、これは出力するメッセージの重要度を示します。主要なレベルは以下の通りです(重要度順)。

  • CRITICAL (50): 重大なエラー。プログラムが停止する可能性がある。
  • ERROR (40): エラー。処理は継続できるが、問題が発生した。
  • WARNING (30): 警告。問題が発生する可能性があるが、現時点では正常に動作している。
  • INFO (20): 情報。プログラムの動作状況を示す。
  • DEBUG (10): デバッグ。開発者向けの低レベル情報。

デフォルトのレベルは `WARNING` です。ロガーレベルを設定するには、`logger.setLevel()` メソッドを使用します。


logger.setLevel(logging.DEBUG)

ロガーレベルよりも重要度の低いメッセージは、たとえ生成されても出力されません。

ハンドラー(Handler)

ハンドラーは、ロガーから受け取ったログメッセージを、どこに、どのように出力するかを決定します。

主要なハンドラーの種類

  • StreamHandler: 標準出力(`sys.stdout`)や標準エラー出力(`sys.stderr`)などのストリームに出力します。
  • FileHandler: ファイルにログを出力します。
  • RotatingFileHandler: ファイルサイズが一定値を超えると、新しいファイルにローテーションしながらログを出力します。
  • TimedRotatingFileHandler: 定期的に(毎日、毎週など)ログファイルをローテーションして出力します。
  • HTTPHandler: HTTP POSTリクエストを使って、ログをリモートサーバーに送信します。

ハンドラーの追加

ロガーにハンドラーを追加するには、`logger.addHandler()` メソッドを使用します。


import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# コンソールに出力するハンドラーを作成
console_handler = logging.StreamHandler()
logger.addHandler(console_handler)

# ファイルに出力するハンドラーを作成
file_handler = logging.FileHandler('app.log')
logger.addHandler(file_handler)

ハンドラーレベル

ハンドラーにもレベルを設定できます。ハンドラーレベルは、そのハンドラーが処理するメッセージの最低限の重要度を決定します。ロガーレベルとハンドラーレベルの両方が満たされた場合に、メッセージが出力されます。


console_handler.setLevel(logging.INFO)
file_handler.setLevel(logging.DEBUG)

この設定では、コンソールにはINFO以上のレベルのメッセージが出力され、ファイルにはDEBUG以上のレベルのメッセージが出力されます。

フォーマッター(Formatter)

フォーマッターは、ログメッセージの出力形式を定義します。タイムスタンプ、ログレベル、ロガー名、メッセージ本文などを、指定した書式で整形します。

フォーマット文字列

フォーマッターは、ログメッセージの各要素をプレースホルダーとして持つフォーマット文字列を使って定義されます。

  • %(asctime)s: ログが生成された時刻
  • %(name)s: ロガーの名前
  • %(levelname)s: ログレベルの名前
  • %(message)s: ログメッセージ本文
  • %(pathname)s: ログメッセージが生成されたソースファイル名
  • %(lineno)d: ログメッセージが生成された行番号

フォーマッターの作成と設定

`logging.Formatter()` クラスを使ってフォーマッターを作成し、`handler.setFormatter()` メソッドでハンドラーに設定します。


formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

ロガーの継承と伝播(Propagation)

ロガーは階層構造を持つため、親ロガーの設定が子ロガーに継承されます。また、`propagate` 属性によって、子ロガーのメッセージが親ロガーに伝播するかどうかを制御できます。

伝播(Propagation)

デフォルトでは、`propagate` は `True` に設定されており、子ロガーのメッセージは親ロガーにも伝播します。これにより、上位のロガーに設定されたハンドラーでも同じメッセージが出力されます。


# my_app.module_a ロガー
logger_a = logging.getLogger('my_app.module_a')
logger_a.setLevel(logging.DEBUG)
handler_a = logging.StreamHandler()
logger_a.addHandler(handler_a)

# my_app ロガー (親)
logger_app = logging.getLogger('my_app')
logger_app.setLevel(logging.WARNING) # 上位のレベルはWARNING
handler_app = logging.StreamHandler()
logger_app.addHandler(handler_app)

logger_a.info('This is an info message from module_a')

この例では、`logger_a` は `INFO` レベルですが、親の `logger_app` は `WARNING` レベルです。`logger_a` の `INFO` メッセージは、`logger_a` のハンドラーには出力されますが、`logger_app` に伝播され、`logger_app` のハンドラーには出力されません。


logger_a.propagate = False # 伝播を無効にする

`propagate` を `False` に設定すると、子ロガーのメッセージは親ロガーに伝播しなくなります。

高度な設定:設定ファイルと辞書

`logging` モジュールは、コード内で直接設定するだけでなく、設定ファイル(INI形式)や辞書形式で設定を記述することも可能です。これにより、設定の管理が容易になり、コードとの分離が図れます。

設定ファイル(`fileConfig`)

`logging.config.fileConfig()` 関数を使って、INI形式の設定ファイルを読み込みます。


# logging_config.ini
[loggers]
keys=root,my_app

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter,detailedFormatter

[logger_root]
level=WARNING
handlers=consoleHandler

[logger_my_app]
level=DEBUG
handlers=fileHandler
qualname=my_app
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=detailedFormatter
args=('app.log',)

[formatter_simpleFormatter]
format=%(levelname)s:%(name)s:%(message)s

[formatter_detailedFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

Pythonコード内では以下のように読み込みます。


import logging.config
import sys

logging.config.fileConfig('logging_config.ini')
logger = logging.getLogger('my_app')
logger.info('This message will be logged to file.')
logger.warning('This message will be logged to both console and file.')

辞書形式(`dictConfig`)

`logging.config.dictConfig()` 関数は、辞書形式でログ設定を記述・適用します。より構造化された設定が可能です。


import logging.config
import sys

LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(levelname)s:%(name)s:%(message)s'
},
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
'stream': 'ext://sys.stdout',
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'detailed',
'filename': 'app.log',
},
},
'loggers': {
'my_app': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False,
},
'root': {
'handlers': ['console'],
'level': 'WARNING',
},
}
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger('my_app')
logger.debug('This is a debug message.')
logger.info('This is an info message.')

`’version’: 1` は必須で、ログ設定のバージョンを示します。`’disable_existing_loggers’: False` は、既存のロガー設定を上書きしないようにします。

ログのローテーション

アプリケーションが長時間稼働したり、大量のログを出力したりする場合、ログファイルが肥大化する可能性があります。`RotatingFileHandler` や `TimedRotatingFileHandler` を使用することで、ログファイルのサイズや時間に基づいて自動的にローテーションさせ、ディスク容量の圧迫を防ぐことができます。

`RotatingFileHandler` の設定例


handler = logging.handlers.RotatingFileHandler(
'app.log',
maxBytes=1024*1024, # 1MB
backupCount=5 # 5つのバックアップファイルを作成
)

`TimedRotatingFileHandler` の設定例


handler = logging.handlers.TimedRotatingFileHandler(
'app.log',
when='midnight', # 毎晩0時にローテーション
interval=1, # 1回
backupCount=7 # 7日分のバックアップ
)

`when` パラメータには、’S’ (秒), ‘M’ (分), ‘H’ (時), ‘D’ (日), ‘W0’-‘W6’ (週の月曜日から日曜日), ‘midnight’ (深夜) などが指定できます。

カスタムレベルとカスタムフォーマット

必要に応じて、独自のログレベルを定義したり、より複雑なフォーマットを実装したりすることも可能です。

カスタムレベル

`logging.addLevelName()` 関数を使って、新しいレベル名と数値を定義できます。


logging.addLevelName(logging.INFO + 1, 'SUCCESS')
logging.addLevelName(logging.INFO - 1, 'VERBOSE')

logger.info + 1('Operation completed successfully.')
logger.info - 1('Detailed step: Processing data.')

カスタムフォーマット

`logging.Formatter` を継承したクラスを作成し、`format()` メソッドをオーバーライドすることで、より高度なフォーマット処理を行えます。

まとめ

`logging` モジュールは、その柔軟な設定オプションにより、様々なアプリケーションのログ記録ニーズに対応できます。ロガー、ハンドラー、フォーマッターの各要素を理解し、適切に組み合わせることで、デバッグ効率の向上、問題発生時の迅速な原因特定、そしてアプリケーションの安定運用に大きく貢献します。設定ファイルや辞書形式の活用は、管理を容易にし、コードの可読性を高める上で有効な手段です。