自作ライブラリ(自作モジュール)のロガーに NullHandler を追加して使用する Python コード例です。
メインモジュールのコードです。
import jisaku
def main():
print('start')
print('jisaku.jisaku_func() start')
jisaku.jisaku_func()
print('jisaku.jisaku_func() end')
print('end')
return
if __name__ == '__main__':
main()
自作ライブラリのコードです(ナルハンドラ付きロガー使用)。
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
def jisaku_func():
logger.debug('logger.debug() 自作ログ DEBUG デバッグ')
logger.info('logger.info() 自作ログ INFO インフォ')
logger.warning('logger.warning() 自作ログ WARNING ウォーニング')
logger.error('logger.error() 自作ログ ERROR エラー')
logger.critical('logger.critical() 自作ログ CRITICAL クリティカル')
return
実行結果の画面表示です。
start
jisaku.jisaku_func() start
jisaku.jisaku_func() end
end
WARNING レベルの以上の深刻なログまで、一切のログを抑制することができました。
もし、logging.NullHandler()
を使わなかったらどうなるのか?
そのときは、WARNING レベル以上の深刻なログだけ、画面に表示されました。
メインモジュールのコードです。
import jisaku
def main():
print('start')
print('jisaku.jisaku_func() start')
jisaku.jisaku_func()
print('jisaku.jisaku_func() end')
print('end')
return
if __name__ == '__main__':
main()
自作ライブラリのコードです。
import logging
logger = logging.getLogger(__name__)
# (NullHandler 無し)
def jisaku_func():
logger.debug('logger.debug() 自作ログ DEBUG デバッグ')
logger.info('logger.info() 自作ログ INFO インフォ')
logger.warning('logger.warning() 自作ログ WARNING ウォーニング')
logger.error('logger.error() 自作ログ ERROR エラー')
logger.critical('logger.critical() 自作ログ CRITICAL クリティカル')
return
実行結果の画面表示です。
start
jisaku.jisaku_func() start
logger.warning() 自作ログ WARNING ウォーニング
logger.error() 自作ログ ERROR エラー
logger.critical() 自作ログ CRITICAL クリティカル
jisaku.jisaku_func() end
end
もし、ロガーを使用したプログラムで『ナルハンドラ付きライブラリ』を呼んだらどうなるのか?
そのときは、いつも通り、すべてのログが取得できました。
メインモジュールのコードです(ロガー使用)。
import logging
import jisaku
logger = logging.getLogger(__name__)
def main():
root = logging.getLogger()
root.setLevel(logging.DEBUG)
sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(sh)
root.debug('start')
logger.debug('jisaku.jisaku_func() start')
jisaku.jisaku_func()
logger.debug('jisaku.jisaku_func() end')
root.debug('end')
return
if __name__ == '__main__':
main()
自作ライブラリのコードです(ナルハンドラ付きロガー使用)。
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
def jisaku_func():
logger.debug('logger.debug() 自作ログ DEBUG デバッグ')
logger.info('logger.info() 自作ログ INFO インフォ')
logger.warning('logger.warning() 自作ログ WARNING ウォーニング')
logger.error('logger.error() 自作ログ ERROR エラー')
logger.critical('logger.critical() 自作ログ CRITICAL クリティカル')
return
実行結果の画面表示です。
root DEBUG: start
__main__ DEBUG: jisaku.jisaku_func() start
jisaku DEBUG: logger.debug() 自作ログ DEBUG デバッグ
jisaku INFO: logger.info() 自作ログ INFO インフォ
jisaku WARNING: logger.warning() 自作ログ WARNING ウォーニング
jisaku ERROR: logger.error() 自作ログ ERROR エラー
jisaku CRITICAL: logger.critical() 自作ログ CRITICAL クリティカル
__main__ DEBUG: jisaku.jisaku_func() end
root DEBUG: end
以上です。
NullHandler の使い道
NullHandler は、基本的には、使う必要は無かったです。
NullHandler
はどんなときに使うのか?用途は?
結論ですが、Python の最高のデフォルトの振る舞い (the best default behaviour) が困るときに、使用するもののようでした。
ロギングの設定については、いくつか考えておくべきこともあります。使っているアプリケーションがロギングを使っていなくて、ライブラリコードがロギングを呼び出すと、(前の節で解説したように) 重大度 WARNING 以上のイベントが、
sys.stderr
に表示されます。これが最高のデフォルトの振る舞いと見なされます。
『深刻なレベルのログは表示する』という振る舞いに対して、そういったログも出さないようにしてくれるのが NullHandler
でした。
自分は、たまに使用したいと思う場面がありましたが、普段は使用する機会がなかったです。
まあ、自分のプログラムから外部ライブラリの名前付きロガーを呼び出して、ナルハンドラを追加してあげる、という使い方もあるとは思います。ですが、そこまでするくらいなら、外部ライブラリのロガーを呼び出してロギングレベルを変更するとか、.propagate
属性を False
に変更するとかしたほうが、適切だと思います。
(Python) logging.Logger.propagate
属性
結局ナルハンドラは、『誰かに使ってもらうようなライブラリ』を作るときに、使用を検討するものだと思いました。
その場合でも、通常はナルハンドラは追加せずに、『最高のデフォルトの振る舞い』のままにしておくのが無難だと思いました。実際、無難だと思います。
NullHandler はロギングには影響しない
外部ライブラリが NullHandler を使用していても、通常のロギングには影響しませんでした。
なので、気軽に追加できる感じでした。
コード例
ロギング設定(ロガーへのハンドラの追加とロギングレベル変更)をしたプログラムで、『NullHandler を使用した外部ライブラリ』を使用した Python コード例です。
メインモジュールです(ロガー使用)。
"""app_main.py"""
import logging
# ナルハンドラ付きロガーを使用している
# 外部ライブラリをインポートします。
import jisaku
# 名前付きロガーを取得します。
# 名前付きロガー取得時のロギングレベルは NOTSET (0) です。
logger = logging.getLogger(__name__)
def main():
# ルートロガーを取得します。
# ルートロガー取得時のロギングレベルは WARNING (30) です。
root = logging.getLogger()
root.setLevel(logging.DEBUG) # WARNING (30) ⇒ DEBUG (10)
# ルートロガーにストリームハンドラを追加します。
# どのハンドラも生成時のロギングレベルは NOTSET (0) です。
sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(sh)
# ナルハンドラ付きロガーを使用した
# 外部ライブラリを呼び出します。
root.debug('start')
logger.debug('jisaku.jisaku_func() start')
jisaku.jisaku_func()
logger.debug('jisaku.jisaku_func() end')
root.debug('end')
print('\n(デバッグ) ロガーとハンドラのロギングレベルを確認します。')
print('root.level: {a} ({b})'.format(
a=logging.getLevelName(root.level),
b=root.level))
print('root.getEffectiveLevel(): {a} ({b})'.format(
a=logging.getLevelName(root.getEffectiveLevel()),
b=root.getEffectiveLevel()))
print(f'sh.level: {logging.getLevelName(sh.level)} ({sh.level})')
print('logger.level: {a} ({b})'.format(
a=logging.getLevelName(logger.level),
b=logger.level))
print('logger.getEffectiveLevel(): {a} ({b})'.format(
a=logging.getLevelName(logger.getEffectiveLevel()),
b=logger.getEffectiveLevel()))
jisaku_logger = logging.getLogger('jisaku')
print('jisaku_logger.level: {a} ({b})'.format(
a=logging.getLevelName(jisaku_logger.level),
b=jisaku_logger.level))
print('jisaku_logger.getEffectiveLevel(): {a} ({b})'.format(
a=logging.getLevelName(jisaku_logger.getEffectiveLevel()),
b=jisaku_logger.getEffectiveLevel()))
return
if __name__ == '__main__':
main()
外部ライブラリです(ナルハンドラ付きロガー使用)。
"""jisaku.py"""
import logging
# 名前付きロガー取得時のロギングレベルは NOTSET (0) です。
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
# (ナルハンドラを追加しました)
def jisaku_func():
logger.debug('logger.debug() 自作ログ DEBUG デバッグ')
logger.info('logger.info() 自作ログ INFO インフォ')
logger.warning('logger.warning() 自作ログ WARNING ウォーニング')
logger.error('logger.error() 自作ログ ERROR エラー')
logger.critical('logger.critical() 自作ログ CRITICAL クリティカル')
return
実行結果
実行結果の画面表示です。
root DEBUG: start
__main__ DEBUG: jisaku.jisaku_func() start
jisaku DEBUG: logger.debug() 自作ログ DEBUG デバッグ
jisaku INFO: logger.info() 自作ログ INFO インフォ
jisaku WARNING: logger.warning() 自作ログ WARNING ウォーニング
jisaku ERROR: logger.error() 自作ログ ERROR エラー
jisaku CRITICAL: logger.critical() 自作ログ CRITICAL クリティカル
__main__ DEBUG: jisaku.jisaku_func() end
root DEBUG: end
(デバッグ) ロガーとハンドラのロギングレベルを確認します。
root.level: DEBUG (10)
root.getEffectiveLevel(): DEBUG (10)
sh.level: NOTSET (0)
logger.level: NOTSET (0)
logger.getEffectiveLevel(): DEBUG (10)
jisaku_logger.level: NOTSET (0)
jisaku_logger.getEffectiveLevel(): DEBUG (10)
意図した通り、ナルハンドラを使用していても、すべてのログを取得することができました。
Python マニュアル
コード例に関係する Python マニュアルの場所です。
モジュールレベルの関数
(Python) logging.getLevelName(level)
ロガー
(Python) logging.getLogger(name=None)
(Python) ロガー名の付け方(上級ロギングチュートリアル)logger = logging.getLogger(__name__)
(Python) logging.Logger.propagate
属性
(Python) logging.Logger.setLevel(level)
『名前付きロガー』取得時のロギングレベルは NOTSET で、『ルートロガー』取得時のロギングレベルは WARNING である旨の説明がありました。
(Python) ロギングレベル (..., logging.DEBUG, logging.INFO, ...)
(Python) logging.Logger.addHandler(hdlr)
(Python) logging.Logger.removeHandler(hdlr)
(Python) logging.Logger.debug(msg, *args, **kwargs)
(Python) logging.Logger.info(msg, *args, **kwargs)
(Python) logging.Logger.warning(msg, *args, **kwargs)
(Python) logging.Logger.error(msg, *args, **kwargs)
(Python) logging.Logger.critical(msg, *args, **kwargs)
ロガーはグローバル変数に入れて使うのが便利でした。普通にグローバルのところで代入しても OK でしたし、global 文を使用してから代入しても OK でした。
もちろん、ローカル変数に入れて使ってもいいと思いますし、logging.getLogger(name=None)
で都度取得して使用してもいいと思います。お好みで。
ハンドラ
ナルハンドラ
(Python) class logging.NullHandler
(Python) 環境設定が与えられないとどうなるか ロガーを使わない(ロギング環境設定が無い)ときの Python の振る舞い(WARNING
以上のログだけは出す)が載っていました。
(Python) ライブラリのためのロギングの設定(NullHandler の使い方) WARNING
以上のログが、ロギングを使っていないときでも sys.stderr
に画面表示される現象を防ぐ、という旨の内容が載っていました。
(Python) 便利なハンドラ(各種ハンドラの日本語の説明)
(Python) class logging.StreamHandler(stream=None)
(Python) logging.Handler.setLevel(level)
『ハンドラ』生成時のロギングレベルは NOTSET(すべてのメッセージが処理される)である旨の説明がありました。
(Python) logging.Handler.setFormatter(fmt)
フォーマッタ
(Python) class logging.Formatter(fmt=None, datefmt=None, style='%', validate=True, *, defaults=None)
(Python) LogRecord 属性 %(asctime)s, %(levelname)s, %(levelno)s, %(message)s, %(name)s, ...
組み込み機能
(Python) __main__
(トップレベルのスクリプト環境)
(Python) __name__
(インポート関連のモジュール属性)
(Python) str.format(*args, **kwargs)
引用符の前に『エフ f 』を付けた文字列
(Python) f''
(f-string、formatted string literal、フォーマット済み文字列リテラル)(言語リファレンス)
以上です。