【Python】logging.NullHandler() の使い方

Python

自作ライブラリ(自作モジュール)のロガーに 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 に表示されます。これが最高のデフォルトの振る舞いと見なされます。

(Python) ライブラリのためのロギングの設定

『深刻なレベルのログは表示する』という振る舞いに対して、そういったログも出さないようにしてくれるのが 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 でした。

(Python) global

もちろん、ローカル変数に入れて使ってもいいと思いますし、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、フォーマット済み文字列リテラル)(言語リファレンス)

以上です。

スポンサーリンク
シェアする(押すとSNS投稿用の『編集ページ』に移動します)
フォローする(RSSフィードに移動します)
スポンサーリンク
シラベルノート
タイトルとURLをコピーしました