はじめに
プログラムやスクリプトは多くの場合、手動での監視なしで実行されます。問題が発生した場合、当時の状況を追跡して原因を特定する必要があります。これを実現するために、プログラムやスクリプトにログ機能を組み込むことが重要です。print文による出力と比較して、loggingモジュールには以下のような利点があります。
- ログの出力時間、ファイル名、関数名、コード行番号、さらにはスレッド名とスレッドIDを記録できる
- ログレベル(デバッグ、情報、警告、エラー、重大エラー)に応じて分類して記録できる
- 画面へのリアルタイム出力とファイルへの出力の両方が可能
基本的な使い方
Pythonでは、標準のloggingモジュールを使用してログを出力します。loggingモジュールの主なコンポーネントはlogger(ログ記録子)です。以下に基本的な使用例を示します。
import logging
logging.debug('デバッグレベルのログ')
logging.info('情報レベルのログ')
logging.warning('警告レベルのログ') # または logging.warn('警告レベルのログ')
logging.error('エラーレベルのログ')
logging.critical('重大エラーレベルのログ')
try:
1/0
except Exception as ex:
logging.exception(ex) # エラーレベルのログで、複数行のトレースバック情報を表示
実行結果は以下の通りです。
WARNING:root:警告レベルのログ
ERROR:root:エラーレベルのログ
CRITICAL:root:重大エラーレベルのログ
ERROR:root:division by zero
Traceback (most recent call last):
File "<ipython-input-5-9be264a94d56>", line 9, in <module>
1/0
ZeroDivisionError: division by zero
warning、error、criticalのログのみが表示されていることがわかります。これは、loggingのデフォルトのログ記録子(root logger)のレベルがlogging.WARNINGに設定されているためです。警告レベル以上のログのみがデフォルトで表示されます。
ログレベルの変更
logging.basicConfigを使用して、root loggerの設定を変更できます。
import logging
logging.basicConfig(level=logging.DEBUG) # グローバルroot loggerの設定
logging.debug('デバッグレベルのログ')
logging.info('情報レベルのログ')
logging.warning('警告レベルのログ')
logging.error('エラーレベルのログ')
logging.critical('重大エラーレベルのログ')
再度実行すると、すべてのログが出力されます。
サポートされているログレベルは以下の通りです:
- logging.NOTSET: 未設定、すべてのレベルのログを出力
- logging.DEBUG: デバッグレベル、すべてのレベルのログを出力
- logging.INFO: 情報レベル、情報レベル以上のログを出力
- logging.WARNING: 警告レベル、警告レベル以上のログを出力
- logging.ERROR: エラーレベル、エラーレベル以上のログを出力
- logging.CRITICAL: 重大エラーレベル、重大エラーレベルのログのみを出力
ログレベルの関係:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
ログフォーマットの変更
ログのデフォルトフォーマットはWARNING:root:警告レベルのログ(レベル:Logger名:メッセージ)です。logging.basicConfigのformatパラメータを使用して出力フォーマットを変更できます。
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug('デバッグレベルのログ')
logging.info('情報レベルのログ')
logging.warning('警告レベルのログ')
logging.error('エラーレベルのログ')
logging.critical('重大エラーレベルのログ')
実行結果:
2020-11-04 10:11:49,238 - DEBUG - デバッグレベルのログ
2020-11-04 10:11:49,238 - INFO - 情報レベルのログ
2020-11-04 10:11:49,238 - WARNING - 警告レベルのログ
2020-11-04 10:11:49,238 - ERROR - エラーレベルのログ
2020-11-04 10:11:49,238 - CRITICAL - 重大エラーレベルのログ
formatでは%(変数名)sのような名前付きプレースホルダを使用して異なる情報を出力し、その他の文字はそのまま表示されます。サポートされている変数は以下の通りです:
- %(levelno)s: ログレベルの数値
- %(levelname)s: ログレベル名
- %(pathname)s: 実行中のプログラムのパス(sys.argv[0])
- %(filename)s: 実行中のプログラム名
- %(funcName)s: ログを記録した関数名
- %(lineno)d: ログを記録した行番号(数字なので%dを使用)
- %(asctime)s: ログの記録時間
- %(thread)d: スレッドID
- %(threadName)s: スレッド名
- %(process)d: プロセスID
- %(message)s: ログメッセージ
日付フォーマットはdatefmtを使用して変更できます。例えば、上記のlogging.basicConfigを以下のように変更します:
...
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y年%m月%d日 %H:%M:%S')
...
出力形式は以下のようになります:
2020年11月04日 10:19:04 - DEBUG - デバッグレベルのログ
2020年11月04日 10:19:04 - INFO - 情報レベルのログ
2020年11月04日 10:19:04 - WARNING - 警告レベルのログ
2020年11月04日 10:19:04 - ERROR - エラーレベルのログ
2020年11月04日 10:19:04 - CRITICAL - 重大エラーレベルのログ
ファイルへの出力
ログはデフォルトで画面に出力されますが、logging.basicConfigのfilenameパラメータを使用してファイルに出力することもできます。
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y年%m月%d日 %H:%M:%S',
filename='run.log',
filemode='a'
)
logging.debug('デバッグレベルのログ')
logging.info('情報レベルのログ')
logging.warning('警告レベルのログ')
logging.error('エラーレベルのログ')
logging.critical('重大エラーレベルのログ')
実行後、ログは画面に出力されず、ファイルに出力されます。filemodeは'w'(上書きモード)と'a'(追記モード)をサポートしています。
カスタムハンドラー
画面とファイルの両方に出力したい場合、logging.basicConfigのhandlersパラメータに2つの異なるハンドラーを追加できます。logging.StreamHandler()は画面出力用、logging.FileHandler()はファイル出力用です。
import logging
cli_handler = logging.StreamHandler() # 画面出力用ハンドラー
file_handler = logging.FileHandler(filename='run.log', mode='a', encoding='utf-8') # ファイル出力用ハンドラー
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y年%m月%d日 %H:%M:%S',
handlers=[cli_handler, file_handler] # 2つのハンドラーを追加
)
logging.debug('デバッグレベルのログ')
logging.info('情報レベルのログ')
logging.warning('警告レベルのログ')
logging.error('エラーレベルのログ')
logging.critical('重大エラーレベルのログ')
これにより、画面とファイルの両方に出力できます。
カスタムLogger
logging.basicConfigはグローバルなroot loggerを直接設定するため、プロジェクト内のすべてのモジュールやサードパーティパッケージに影響を与えます。他のモジュールやサードパーティパッケージのログ出力に影響を与えないようにするために、カスタムLogger(ログ記録子)を使用できます。基本的な手順は以下の通りです:
- logging.getLogger()を使用して新しいloggerオブジェクトを作成し、そのログレベル(総合ログスイッチ)を設定する
- 複数のログハンドラーを作成し、それぞれのフォーマットとログレベルを設定する
- 複数のログハンドラーをloggerに追加する
コード例:
# ファイル名: mylogger.py
import logging
def get_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG) # 総合ログレベルの設定
format = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y年%m月%d日 %H:%M:%S') # ログフォーマット
cli_handler = logging.StreamHandler() # 画面出力用ハンドラー
file_handler = logging.FileHandler(filename='run.log', mode='a', encoding='utf-8') # ファイル出力用ハンドラー
cli_handler.setFormatter(format) # 画面ログフォーマットの設定
file_handler.setFormatter(format) # ファイルログフォーマットの設定
cli_handler.setLevel(logging.INFO) # 画面ログレベルの設定(loggerの総合ログレベルより大きく設定可能)
# file_handler.setLevel(logging.DEBUG) # デフォルトではloggerのレベルを使用
logger.handlers.clear() # 既存のハンドラーをクリア(他のloggerから継承したハンドラーを避ける)
logger.addHandler(cli_handler) # 画面ログハンドラーをloggerに追加
logger.addHandler(file_handler) # ファイルログハンドラーをloggerに追加
return logger
使用方法としては、このモジュールのget_loggerメソッドをインポートし、loggingの代わりにloggerを使用して各種情報を出力します。
from mylogger import get_logger
logger = get_logger('mylogger')
logger.debug('デバッグレベルのログ')
logger.info('情報レベルのログ')
logger.warning('警告レベルのログ')
logger.error('エラーレベルのログ')
logger.critical('重大エラーレベルのログ')
注意点:loggerはサブモジュールを継承する性質があるため、プロジェクト内の複数の場所でget_loggerを使用して異なるloggerオブジェクトを生成すると、他のloggerのハンドラーを継承する可能性があります。プロジェクト内で同じloggerを使用することをお勧めします。または、loggerにシングルトンパターンを使用することも可能です。 サーバーサイドプロジェクト(Webプロジェクトなど)では、プロセスが長時間実行されるため、ログファイルを分割するためにローテーションログハンドラーを使用できます。