こんにちは、将来の夢が最近「ログハウスのサウナ小屋を建てたい」に変わった、とりゅふです。
logという単語は、「丸太」という意味なのですが、ITの世界では、情報の履歴、記録といった意味を持ちます。
blogもweb logの略です。
このlogの語源ですが、時は大航海時代、船乗り達が自分の船の速度を測るために、丸太を船首から海に流し、船尾まで流れる時間を「航海日誌(logbook)」を記録していたことが由来します。
閑話休題、本日のテーマはこちら。
よくPythonのコード書いてデバッグ実行するのに、いろんなところにprint()
を書いて確認してしまいがちですが、Pythonを始め、各種プログラミング言語にはログ出力用のモジュールがちゃんと備わっています。
今日はこのロギングについて、改めて真剣に向き合って行こうと思います。
※本記事はPython3.7.11で検証しています。
loggingの基本
ログを出力する
Pythonのログは、logging
モジュールで出力することができます。
import logging logging.basicConfig(level=logging.DEBUG) logging.debug('デバッグ') logging.info('インフォ') logging.warning('ワーニング') logging.error('エラー') logging.critical('クリティカル')
DEBUG:root:デバッグ INFO:root:インフォ WARNING:root:ワーニング ERROR:root:エラー CRITICAL:root:クリティカル
ログレベル
Pythonはprint()
で情報をコンソール出力することができますが、このlogging
を用いることで、ログレベルを設定し、わかりやすい情報を残すことができます。ログレベルとは、ログの重要度を示すものです。以下公式ドキュメントからの引用です。
レベル | いつ使うか |
---|---|
DEBUG | おもに問題を診断するときにのみ関心があるような、詳細な情報。 |
INFO | 想定された通りのことが起こったことの確認。 |
WARNING | 想定外のことが起こった、または問題が近く起こりそうである (例えば、'disk space low') ことの表示。 |
ERROR | より重大な問題により、ソフトウェアがある機能を実行できないこと。 |
CRITICAL | プログラム自体が実行を続けられないことを表す、重大なエラー。 |
https://docs.python.org/ja/3/howto/logging.html より引用
ログレベルは、デフォルトではWARNINGのみを表示するようになっています。DEBUGやINFOはそのままではコンソール出力されません。
そのため、先程のプログラムの3行目のlogging.basicConfig(level=logging.DEBUG)
の箇所で、DEBUG以上のログレベルを出力するように設定しています。
ログのフォーマット
次にログのフォーマットを変更してみます。
フォーマットは、`logging.basicConfig(format='ここにフォーマットを指定')
で設定できます。
日付やログレベルなどのパラメータも設定可能です。パラメータの詳細は下記で確認できます。
logging --- Python 用ロギング機能 — Python 3.10.0b2 ドキュメント
import logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(filename)s, lines %(lineno)d. %(message)s') logging.debug('デバッグ') logging.info('インフォ') logging.warning('ワーニング') logging.error('エラー') logging.critical('クリティカル')
2021-11-15 21:16:20,591 [DEBUG] main.py, lines 7. デバッグ 2021-11-15 21:16:20,591 [INFO] main.py, lines 8. インフォ 2021-11-15 21:16:20,591 [WARNING] main.py, lines 9. ワーニング 2021-11-15 21:16:20,591 [ERROR] main.py, lines 10. エラー 2021-11-15 21:16:20,592 [CRITICAL] main.py, lines 11. クリティカル
ログのファイル出力
デフォルトではログはコンソールに出力されますが、もちろんファイル出力もできます。
、`logging.basicConfig(filename='ここにファイルパスを指定')
で設定することで、コンソールには出力されず、ファイルに出力されるようになります。
import logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(filename)s, lines %(lineno)d. %(message)s', filename='logs/test.log') logging.debug('デバッグ') logging.info('インフォ') logging.warning('ワーニング') logging.error('エラー') logging.critical('クリティカル')
Loggerを使う
loggingとLoggerの違い
これまでlogging
モジュールの設定をいじってきましたが、
共通モジュールを直接いじらず、logging.Logger
クラスのインスタンスを生成し、利用しましょう。
Loggerクラスの基本
以下サンプルコードです。
Logger
クラスも、logging
と同様にログレベル、フォーマット、出力先などを制御できます。
import logging # ロガーインスタンス生成 logger = logging.getLogger(__name__) # ロガーにログレベル設定(ここをDEBUGに設定しないと、その後ハンドラでDEBUG設定しても出力されない) logger.setLevel(logging.DEBUG) # ログフォーマット設定 format = '%(asctime)s [%(levelname)s] %(filename)s, lines %(lineno)d. %(message)s' date_format = '%Y-%m-%d %H:%M:%S' formatter = logging.Formatter(format, date_format) # コンソール出力ハンドラを設定、DEBUG以上のレベルを出力 stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.DEBUG) stream_handler.setFormatter(formatter) logger.addHandler(stream_handler) # ファイル出力ハンドラを設定、INFO以上のレベルを出力 file_handler = logging.FileHandler('logs/test2.log') file_handler.setLevel(logging.INFO) file_handler.setFormatter(formatter) logger.addHandler(file_handler) # ログ出力 logger.debug('デバッグ') logger.info('インフォ') logger.warning('ワーニング') logger.error('エラー') logger.critical('クリティカル')
2021-11-15 21:30:40 [DEBUG] main2.py, lines 25. デバッグ 2021-11-15 21:30:40 [INFO] main2.py, lines 26. インフォ 2021-11-15 21:30:40 [WARNING] main2.py, lines 27. ワーニング 2021-11-15 21:30:40 [ERROR] main2.py, lines 28. エラー 2021-11-15 21:30:40 [CRITICAL] main2.py, lines 29. クリティカル
2021-11-15 21:30:40 [INFO] main2.py, lines 26. インフォ 2021-11-15 21:30:40 [WARNING] main2.py, lines 27. ワーニング 2021-11-15 21:30:40 [ERROR] main2.py, lines 28. エラー 2021-11-15 21:30:40 [CRITICAL] main2.py, lines 29. クリティカル
例外処理でログ出力する
これまではテキストのみをログ出力してきましたが、例外発生時にエラーの詳細、スタックトレースを出力したい場合はどうなるでしょう?
例外を出力するには、logger.exception(e)
を用います。以下サンプルコードです。
import logging # ロガーインスタンス生成 logger = logging.getLogger(__name__) # ロガーにログレベル設定 logger.setLevel(logging.DEBUG) # ログフォーマット設定 format = '%(asctime)s [%(levelname)s] %(filename)s, lines %(lineno)d. %(message)s' date_format = '%Y-%m-%d %H:%M:%S' formatter = logging.Formatter(format, date_format) # ファイル出力ハンドラを設定、INFO以上のレベルを出力 file_handler = logging.FileHandler('logs/test3.log') file_handler.setLevel(logging.INFO) file_handler.setFormatter(formatter) logger.addHandler(file_handler) # ログ出力 try: logger.info('ゼロ乗算をします') n = 1 / 0 except Exception as e: logger.critical('致命的なエラーが発生しました') logger.exception(e)
2021-11-15 21:38:26 [INFO] main3.py, lines 23. ゼロ乗算をします 2021-11-15 21:38:26 [CRITICAL] main3.py, lines 26. 致命的なエラーが発生しました 2021-11-15 21:38:26 [ERROR] main3.py, lines 27. division by zero Traceback (most recent call last): File "main3.py", line 24, in <module> n = 1 / 0 ZeroDivisionError: division by zero
複数のモジュールでloggerを共有する
最後に複数モジュールで1つのloggerを共有してみます。
log.py
でLogger
インスタンスを生成し、main.py
と、sub.py
でログ出力します。
├── log.py ├── logs │ └── common.log ├── main.py └── sub.py
- log.py
import logging # ロガーインスタンス生成 logger = logging.getLogger(__name__) # ロガーにログレベル設定 logger.setLevel(logging.DEBUG) # ログフォーマット設定 format = '%(asctime)s [%(levelname)s] %(filename)s, lines %(lineno)d. %(message)s' date_format = '%Y-%m-%d %H:%M:%S' formatter = logging.Formatter(format, date_format) # ファイル出力ハンドラを設定、INFO以上のレベルを出力 file_handler = logging.FileHandler('logs/common.log') file_handler.setLevel(logging.INFO) file_handler.setFormatter(formatter) logger.addHandler(file_handler)
- main.py
from log import logger import sub try: logger.info('mainを実行します') sub.execute() except Exception as e: logger.critical('mainで致命的なエラーが発生しました') logger.exception(e) logger.info('mainを終了します')
- sub.py
from log import logger def execute(): logger.info('subを実行します') raise Exception('sub内で例外が発生しました。')
2021-11-15 21:41:13 [INFO] main.py, lines 5. mainを実行します 2021-11-15 21:41:13 [INFO] sub.py, lines 5. subを実行します 2021-11-15 21:41:13 [CRITICAL] main.py, lines 8. mainで致命的なエラーが発生しました 2021-11-15 21:41:13 [ERROR] main.py, lines 9. sub内で例外が発生しました。 Traceback (most recent call last): File "main.py", line 6, in <module> sub.execute() File "sub.py", line 6, in execute raise Exception('sub内で例外が発生しました。') Exception: sub内で例外が発生しました。 2021-11-15 21:41:13 [INFO] main.py, lines 10. mainを終了します
まとめ
本日はPythonのlogging
とLogger
についてまとめました。
プログラムを効率良く書いたり、実際の業務でシステムを滞りなく運用するためには、ログ出力を理解することが書かせません。
ログ出力を理解し、より品質の高いプログラムを書けるようにしましょう。