Pythonにおけるログモジュールの活用法

はじめに

プログラムやスクリプトは多くの場合、手動での監視なしで実行されます。問題が発生した場合、当時の状況を追跡して原因を特定する必要があります。これを実現するために、プログラムやスクリプトにログ機能を組み込むことが重要です。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(ログ記録子)を使用できます。基本的な手順は以下の通りです:

  1. logging.getLogger()を使用して新しいloggerオブジェクトを作成し、そのログレベル(総合ログスイッチ)を設定する
  2. 複数のログハンドラーを作成し、それぞれのフォーマットとログレベルを設定する
  3. 複数のログハンドラーを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プロジェクトなど)では、プロセスが長時間実行されるため、ログファイルを分割するためにローテーションログハンドラーを使用できます。

タグ: Python logging ログ管理 デバッグ

8月6日 03:43 投稿