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