はじめに
久美子部長と滝先生の誕生日に挟まれた今日8/22は、タモリの誕生日。おめでとうございます。
nikkieです。
Pythonの標準ライブラリloggingのgetLogger()
関数を使って得たロガーの扱い方の自分用まとめです。
前提として、他のユーザによって使われるライブラリを開発しており、ライブラリの中でのロギングを書こうとしています。
目次
結論:getLogger()
で得たロガーにNullHandler
を設定する
ロガーを仕込むライブラリ
src/awesomelib/__init__.py
import logging logger = logging.getLogger(__name__) logger.setLevel(logging.WARNING) logger.addHandler(logging.NullHandler()) def awesome() -> None: logger.warning("ちょっとヤバいよ") print("Awesome ✨")
ライブラリを使うコード
import logging from awesomelib import awesome logging.basicConfig( format="%(asctime)s | %(levelname)s | %(name)s:%(funcName)s:%(lineno)d - %(message)s", ) awesome()
出力結果
2024-08-22 23:40:05,108 | WARNING | awesomelib:awesome:9 - ちょっとヤバいよ Awesome ✨
※ただし執筆時点で詰めきれていない積み残しあり(変更履歴が分かるようにアップデートします)
(2023/08/26 追記)
ライブラリのロガーはsetLevel
せず、NOTSET
レベルのまま使うのがよいように思われます(「ライブラリのためのロギングの設定」のコードにより近づきます)
自作のPythonライブラリでloggingを使うとき、ロガーにsetLevelしない? ログレベルNOTSETのロガーの動きを理解する - nikkie-ftnextの日記
(2023/08/26 追記終わり)
コード解説
まとめ
上記のコードに込めた意図です
- ライブラリは子のロガーを設定して使う
logging.getLogger(__name__)
- 子のロガーがログをハンドラに送るlevelを設定
- ハンドラは何もしない
- ライブラリのユーザはルートロガーを設定して使う
logging.basicConfig()
- ルートロガーが出力するハンドラとそのフォーマットを指定した(標準出力)
ライブラリのロガー
ドキュメントの以下に従っています。
「ライブラリのためのロギングの設定」
https://docs.python.org/ja/3/howto/logging.html#configuring-logging-for-a-library
何らかの理由で、ロギング設定がなされていないときにメッセージを表示 させたくない なら、あなたのライブラリのトップレベルロガーに何もしないハンドラを取り付けられます。
logging.getLogger(__name__)
はlogging.getLogger("awesomelib")
なので1、ライブラリのトップレベルロガーです。
ここに何もしないロガーNullHandler()
を設定しています。
ロガーのレベルをWARNING
としました。
WARNING
以上のレベルのログがハンドラに渡ります。
ここでハンドラは何もしないので、WARNING
以上のレベルのログは何もされず、どこにも出力されません。
WARNING
以上のレベルのログ:ロガーが処理してハンドラが呼び出されるが、ハンドラの処理ではどこにも出力しないWARNING
未満のレベルのログ:ロガーは処理しない(そもそもハンドラは呼ばれない)
よって、どのようなログも(WARNING以上は処理されるのですが)出力されることはありません。
ライブラリのユーザのロガー
ルートロガーを設定します。
ルートロガーは全てのロガーの親です。
すなわち、ライブラリのロガーの親でもあります。
ロガーの親子関係には、propagate(伝播)という仕組みがあります。
子のロガーが処理するログは、親のロガーにも(そのまた親にも)伝播します。
ルートロガーはすべての親なので、ライブラリのロガーが処理するログはルートロガーにも届きます。
上記のlogging.basicConfig()
では、ルートロガーのレベルをWARNING
とし、さらに、標準出力するハンドラを設定しました。
参考:Pythonのloggingモジュールのドキュメントの「もっとも単純な例」を説明する 〜logging.warningの裏側で起こっていること〜 - nikkie-ftnextの日記
これにより
- ライブラリのロガーが
WARNING
以上のレベルのログを処理する- (ライブラリのロガーのハンドラは何もしない)
- 親であるルートロガーも同じログを処理する
- ハンドラが標準出力に出力2
結果として、ライブラリのロガーのハンドラは出力しませんが、propagateのおかげでルートロガーのハンドラが出力します!
同じ内容を扱った過去記事(「ロガーの階層関係をもとに、ここまでのロギングを理解する」など参照)
propagateを含め、ロガーの親子関係について書いた過去記事
(※本記事で扱っていないフィルタという概念も登場します)
積み残し事項
- ✅ロガーやハンドラのlevelをNOTSETに指定
- ✅ライブラリでNullHandlerを設定しない場合もある
- 例:httpx(ハンドラを設定していないように見える)
- 👉最終手段ハンドラなるもの(上記記事参照)
終わりに
現時点の私が考える、Pythonで書いたライブラリの中のロギングのコードでした。
ロガーの親子関係(propagate)を利用し、子ロガーは何もしないハンドラを設定。
親ロガーへの伝播を利用して、ルートロガーのハンドラでログを処理させるのがよいと考えています。
ここまで理解するのに私は全然スムーズにいかなかったので、今後見返して出発点とするための記事でした。
PyCon mini Shizuokaの発表準備でもあります。
変更履歴
- 2024/08/26
- 積み残し「ロガーやハンドラのlevelをNOTSETに指定」解消
- 合わせてライブラリのロガーはsetLevelしないことを結論に追加
- 積み残し「ライブラリでNullHandlerを設定していない場合」解消
- 積み残し「ロガーやハンドラのlevelをNOTSETに指定」解消
- 「モジュールの名前」参照 https://docs.python.org/ja/3/reference/datamodel.html#modules↩
-
途中で出した「もっとも単純な例」の記事より、レベルが
NOTSET
です(一つの記事にまとめきれなかったので落ち穂拾い記事を書きます)↩