logging(logger) ログ出力 その2

前回の続きでloggerについて学習します。少し用語が多くて複雑ですが、logの出力先、ログレベルを出力箇所、目的に応じて柔軟に出力するために便利な機能であるため、整理して学習していきましょう。

loggingが提供する部品

まずは用語を抑えましょう。loggingはロガー、ハンドラ、フィルタ、フォーマッタと呼ばれるカテゴリの部品を提供します。それぞれ、公式ドキュメントでは以下のような説明がなされています。

  • ロガーは、アプリケーションコードが直接使うインタフェースを公開します。
  • ハンドラは、(ロガーによって生成された) ログ記録を適切な送信先に送ります。
  • フィルタは、どのログ記録を出力するかを決定する、きめ細かい機能を提供します。
  • フォーマッタは、ログ記録が最終的に出力されるレイアウトを指定します。

これだけだと、それぞれ何のことかわからないと思いますが、実際に書いて動かしてみると腑に落ちると思います。

なお、ログを出力するかどうかはフィルタオブジェクトに基づいて決定されるのですが、基本的にフィルタはデフォルト、つまり前ページにてご説明したログレベル(深刻度)をそのまま使用することが多いため、まずはロガー、ハンドラー、フォーマッタの関係について学習しましょう。

ロガーを使ってログを出力してみる

では、動かしてみる前に超ざっくり説明です。ロガーはログ出力のためにプログラム上で呼び出すオブジェクトです。ハンドラはログの出力先を定めます。フォーマッタはログ出力形式を定めます。抑えておくと良いのが、「ロガーオブジェクトは複数のハンドラ(≒出力先)を持ち、ハンドラは1つのフォーマッタを持つ。」ということです。プログラム上に記述する場合は(実際に実装する際は多少順番が前後すると思いますが)以下の流れとなります。

  1. ロガーを取得
  2. ハンドラーを生成
  3. フォーマッタを生成
  4. ハンドラーにフォーマッタを設定
  5. ロガーにハンドラーを設定

ロガーの生成には、loggingが提供するgetLoggerメソッドを使用します。ロガーには名前を定義でき、getLoggerメソッドでその名前を定義することができます。一般的にモジュール名が格納された__name__を使用します。ハンドラーの生成にはハンドラーの種類に応じたクラスを指定します。サンプルを見てみましょう。

import logging

# 1. ロガーを取得する
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 出力レベルを設定

# 2. ハンドラーを生成する
h = logging.StreamHandler()
h.setLevel(logging.DEBUG) # 出力レベルを設定

# 3. フォーマッタを生成する
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 4. ハンドラーにフォーマッターを設定する
h.setFormatter(fmt)

# 5. ロガーにハンドラーを設定する
logger.addHandler(h)

# ログ出力を行う
logger.info("ログを出力")

ハンドラーには出力先に応じて様々な種類があります。上のサンプルで使用しているStreamHandlerは標準出力に出力します。

さて、出力レベルを2箇所で設定していますが、これには理由があります。先ほど「ロガーは複数のハンドラを設定できる」と書きましたがそれを念頭に以下のサンプルを見てみてください。

import logging

# ロガーを取得する
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 出力レベルを設定

# ハンドラー1を生成する
h1 = logging.StreamHandler()
h1.setLevel(logging.DEBUG) # 出力レベルを設定

# ハンドラー2を生成する
h2 = logging.FileHandler('sample.log')
h2.setLevel(logging.ERROR) # 出力レベルを設定

# フォーマッタを生成する
fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# ハンドラーにフォーマッターを設定する
h1.setFormatter(fmt)
h2.setFormatter(fmt)

# ロガーにハンドラーを設定する
logger.addHandler(h1)
logger.addHandler(h2)

# ログ出力を行う
logger.debug("degubログを出力")
logger.info("infoログを出力")
logger.warn("warnログを出力")
logger.error("errorログを出力")

1つのロガーにハンドラーを複数登録しています。2つめのFileHandlerは指定したファイルにログを出力するハンドラーです。ハンドラー1、ハンドラー2で設定されているレベルが異なる点に注意してください。上のコードを実行してみると、標準出力ではDEBUG〜ERRORまで出力されますが、ログファイル、つまりハンドラー2の方はERRORログしか出力されません。また、5行目のロガーの出力レベルをERRORに変更すると、標準出力もファイルの方もともにERRORログしか出力されなくなります。ロガーの方の出力レベルは元栓を調整する感じですね。