こんにちは。
本記事は株式会社インティメート・マージャーのAdvent Calendar 2024 14日目の記事になります。
今回は例外(exception)をログ出力する際のポイントをまとめたいと思います。
TL;DR
logger.exception()を使いましょう
そもそもloggerって?
エラーなどのログを出力,記録することができるLoggingというモジュールで使われるオブジェクトです。このloggerオブジェクトを用いてログの出力などを行います。
print()を使ってデバッグを行った経験がある方も多いと思いますが、print()を使った方法よりも柔軟にログの出力を行うことができます。
Logging HOWTO
ロギングレベル
print()との違いの一つに、レベルという概念があります。これは重要度の高さを表し、NOTET,DEBUG,INFO,WARNING,ERROR,CRITICALの順に重要度が高くなっていきいます。
loggerにlogger.setLevel(level)の形で閾値となるレベルを設定することで、設定したレベル以上のログのみを出力することができます。
実際にログを出力する際は、logger.INFO()のような形で出力を行います。
各ログにレベルを適切に設定することで、用途に応じてどこまでログを出力するかの操作を簡単に行うことができます。
例外
さて、loggerで例外をログ出力する話をする前に、pythonの例外について少し話します。
実行中に検出されたエラーを、構文エラー(構文解析エラー)に対して例外(exception)と呼びます。
零除算を行ってしまった際のZeroDivisionErrorや、存在しない属性にアクセスしようとした際のAttributeError、辞書型で存在しないキーにアクセスしようとした際のKeyErrorなど、よく遭遇するものを挙げるだけでも多くの種類があります。
これらの例外は、例外なくBaseExceptionクラスの派生であり、多くの種類の例外を同じ方法で処理することができます。
例外は致命的でないものもあり、例外が発生した際は適切に処理する必要があります。
多くの場合、try~exceptを用いて例外の対応を行います。
どの例外が発生したかによって処理を分けるといったことも行え、多くの場合以下のように例外の種類ごとに異なった処理を行います。必ずしも例外の種類を事前に特定できるとは限りませんが、その際はExceptionを指定することで例外を拾うことができます。
try:
# 例外を送出する可能性のある処理
except ZeroDivisionError:
# 零除算例外が送出された場合の処理
except AttributeError:
# アトリビュートエラーが送出された場合の処理
except Exception:
# その他の例外が送出された場合の処理
このexcept内にloggerの処理を記述することでエラーログの出力を行います。
loggerによる例外のログ出力
さて、それでは本題に入ります。
大切なのはloggerによってどの情報をログ出力するのか、ということです。エラーログを出力するということは、基本的にはエラーの解析が目的であり、情報が不足していると十分な解析を行うことができません。
そこで、logger.exception()を使うことを推奨します。
except内でlogger.exception("メッセージ")と書くことで、その場所でどのような例外が送出されているのかをログ出力してくれるのみならず、深い階層で発生した例外についても遡ってログとして出力してくれます。
遡ってログ出力とは?
深い階層でZeroDivisionErrorを発生させて、その例外をログ出力する例を挙げます。
import logging
# ログ出力用のロガー設定
logger = logging.getLogger("examples")
def zero_division():
1/0
def f():
zero_division()
try:
f()
except Exception:
logger.exception("error!")
error!
Traceback (most recent call last):
File "main.py", line 15, in <module>
f()
File "main.py", line 11, in f
zero_division()
File "main.py", line 8, in zero_division
1/0
ZeroDivisionError: division by zero
logger.exception()に記述したメッセージがまず出力され、その後にstack traceの内容が出力されています。
このように、logger.exception()を使用することでクラスの継承や複雑な関数で例外が発生した際もその原因をたどりやすくなります。
おまけ
実はlogger.error("メッセージ",exc_info=True)とすることで、logger.exception("メッセージ")と等価なログ出力を行うことができます。
とはいえ、logger.exception()を使う方がシンプルで意図が伝わりやすいとおもいます。
まとめ
本記事では、loggerによる例外のログ出力の方法を説明しました。
非常にシンプルなコードでログの出力を行えることが理解できたかと思います。