web-dev-qa-db-ja.com

レイジーロガーメッセージ文字列の評価

私は標準pythonロギングモジュールを使用していますpythonアプリケーション:

インポートロギング
 logging.basicConfig(level = logging.INFO)
 logger = logging.getLogger( "log")
 while True:
 logger.debug( '愚かなログメッセージ "+' '.join([str(i)for i in range(20)]))
#何かする

問題は、デバッグレベルが有効になっていなくても、その愚かなログメッセージがループの反復ごとに評価されるため、パフォーマンスに悪影響を与えることです。

これに対する解決策はありますか?

C++には、次のようなマクロを提供する_log4cxx_パッケージがあります。
LOG4CXX_DEBUG(logger, messasage)
効果的に評価されます

 if(log4cxx :: debugEnabled(logger)){
 log4cxx.log(logger、log4cxx :: LOG4CXX_DEBUG、message)
} 

しかし、Python(AFAIK))にはマクロがないので、ロギングを行うための効率的な方法はありますか?

64
Zaar Hai

ロギングモジュールは、あなたがやりたいことをすでに部分的にサポートしています。これを行う:

log.debug("Some message: a=%s b=%s", a, b)

... これの代わりに:

log.debug("Some message: a=%s b=%s" % (a, b))

ロギングモジュールは、メッセージが実際にどこかでログに記録されない限り、完全なログメッセージを生成しないほどスマートです。

この機能を特定のリクエストに適用するには、lazyjoinクラスを作成します。

class lazyjoin:
    def __init__(self, s, items):
        self.s = s
        self.items = items
    def __str__(self):
        return self.s.join(self.items)

これを次のように使用します(生成式の使用に注意して、遅延を追加してください)。

logger.info('Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20))))

これがこの作品を示すデモです。

>>> import logging
>>> logging.basicConfig(level=logging.INFO)
>>> logger = logging.getLogger("log")
>>> class DoNotStr:
...     def __str__(self):
...         raise AssertionError("the code should not have called this")
... 
>>> logger.info('Message %s', DoNotStr())
Traceback (most recent call last):
...
AssertionError: the code should not have called this
>>> logger.debug('Message %s', DoNotStr())
>>>

デモでは、logger.info()呼び出しがアサーションエラーにヒットしましたが、logger.debug()はそれほど遠くまで到達しませんでした。

70
Shane Hathaway

もちろん、次のものはマクロほど効率的ではありません。

if logger.isEnabledFor(logging.DEBUG):
    logger.debug(
        'Stupid log message ' + ' '.join([str(i) for i in range(20)])
    )

しかし、単純な 遅延評価 であり、受け入れられた回答(-===-)より4倍高速です

class lazyjoin:
    def __init__(self, s, items):
        self.s = s
        self.items = items

    def __str__(self):
        return self.s.join(self.items)

logger.debug(
    'Stupid log message %s', lazyjoin(' ', (str(i) for i in range(20)))
)

私の設定については benchmark-src を参照してください。

34
schnittstabil
import logging
import time

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("log")

class Lazy(object):
    def __init__(self,func):
        self.func=func
    def __str__(self):
        return self.func()

logger.debug(Lazy(lambda: time.sleep(20)))

logger.info(Lazy(lambda: "Stupid log message " + ' '.join([str(i) for i in range(20)])))
# INFO:log:Stupid log message 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

スクリプトを実行すると、最初のlogger.debugコマンドの実行に20秒はかかりません。これは、ロギングレベルが設定レベルを下回っている場合、引数が評価されないことを示しています。

24
unutbu

シェーンが指摘するように、

_log.debug("Some message: a=%s b=%s", a, b)
_

... これの代わりに:

_log.debug("Some message: a=%s b=%s" % (a, b))
_

メッセージが実際にログに記録される場合、文字列のフォーマットのみを実行することにより、時間を節約します。

ただし、次のように値を文字列にフォーマットするために前処理する必要がある場合があるため、これで問題が完全に解決するわけではありません。

_log.debug("Some message: a=%s b=%s", foo.get_a(), foo.get_b())
_

その場合、ロギングが発生しない場合、obj.get_a()およびobj.get_b()が計算されますeven

これに対する解決策はラムダ関数を使用することですが、これにはいくつかの追加の機械が必要です:

_class lazy_log_debug(object):
    def __init__(self, func):
        self.func = func
        logging.debug("%s", self)
    def __str__(self):
        return self.func()
_

...次に、以下を使用してログに記録できます。

_lazy_log_debug(lambda: "Some message: a=%s b=%s" % (foo.get_a(), foo.get_b()))
_

その場合、_log.debug_がフォーマットを実行することを決定した場合、ラムダ関数はonlyが呼び出されるため、___str___メソッドが呼び出されます。

注意してください:そのソリューションのオーバーヘッドはメリットをはるかに超える可能性があります:-)しかし、少なくとも理論的には、完全に遅延したロギングを行うことができます。

13
Pierre-Antoine

私が提示します、Lazyfy

class Lazyfy(object):
    __slots__ = 'action', 'value'

    def __init__(self, action, *value):
        self.action = action
        self.value = value

    def __str__(self):
        return self.action(*self.value)

使用法:

from pprint import pformat
log.debug("big_result: %s", Lazyfy(pformat, big_result))
log.debug( "x y z: %s", Lazyfy( lambda x, y, z: ' ,'.join( [x, y, z] ), '1', '2', '3' ) )

元の例:

logger.info('Stupid log message %s', Lazyfy(lambda: ' '.join((str(i) for i in range(20)))))

ご覧のとおり、これはラムダ関数を使用する他の回答もカバーしていますが、value属性と展開でより多くのメモリを使用します。ただし、次のようにするとより多くのメモリを節約できます __ slots__の使用法

最後に、断然、最も効率的なソリューションは、別の答えが示唆されているように、次のとおりです。

if logger.isEnabledFor(logging.DEBUG): 
    logger.debug('Stupid log message ' + ' '.join([str(i) for i in range(20)]))
0
user

グローバル状態属性へのアクセスのみに依存している場合は、pythonクラスをインスタンス化し、__str__メソッドを使用して遅延化できます。

class get_lazy_debug(object):
    def __repr__(self):
        return ' '.join(
                str(i) for i in range(20)
            )

# Allows to pass get_lazy_debug as a function parameter without 
# evaluating/creating its string!
get_lazy_debug = get_lazy_debug()

logger.debug( 'Stupid log message', get_lazy_debug )

関連:

  1. Pythonで条件付きでデバッグされるステートメント
  2. Pythonのメタクラスとは
0
user