懒惰的记录器消息字符串评估


68

我在我的python应用程序中使用标准的python日志记录模块:

导入日志
logging.basicConfig(level = logging.INFO)
logger = logging.getLogger(“ log”)
而True:
  logger.debug('愚蠢的日志消息“ +''.join([str(i)for range(20)中的i))
  # 做点什么

问题是,尽管未启用调试级别,但在每次循环迭代时都会评估该愚蠢的日志消息,这会严重损害性能。

有什么解决办法吗?

在C ++中,我们log4cxx提供了提供以下宏的软件包:
LOG4CXX_DEBUG(logger, messasage)
有效评估为

如果(log4cxx :: debugEnabled(logger)){
    log4cxx.log(logger,log4cxx :: LOG4CXX_DEBUG,消息)
}

但是,由于Python(AFAIK)中没有宏,是否有一种有效的日志记录方法?

Answers:


85

日志记录模块已经对您要执行的操作提供了部分支持。做这个:

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()并没有解决。


哇,真有趣!python> = 3.5 f字符串呢?在那里,它并不懒惰(我测试过)
Denny Weinberg

1
有关f字符串的信息,请参见stackoverflow.com/a/49884004/1783801,此处也应适用。
Jaleks '20

38

当然,以下内容不如宏有效:

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


3
简单高效。我喜欢。这应该获得更多的赞誉。
Rockallite 2014年

效率取决于手头的情况,您应该始终对自己的方案进行基准测试。就我而言,惰性日志记录不需要任何参数,但是可以在调用时从类中收集东西__str__。所以基本上,我得到了几乎相同的结果。在这里
Guyarad

@guyarad:您也花了创建lazyjoin实例所需的时间。另请参阅我对Python的回答:如何进行延迟调试日志记录
schnittstabil '16

@schnittstabil不一定。您可以查看我的完整描述。就我而言,我不需要参数,只需要一个方法调用。因此,我可以一次创建惰性对象,然后传递对象本身(不创建对象)。相比“如果”建议,事情变得更糟了。你可以检查我的要点根据你要点我。
Guyarad '16

25
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秒即可执行。这表明在日志记录级别低于设置级别时不评估该参数。


13

正如Shane指出的那样,

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()将被计算,即使在没有日志记录也会。

解决方案是使用lambda函数,但这需要一些额外的设备:

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决定执行格式化后才调用lambda函数,从而调用该__str__方法。

请注意:该解决方案的开销可能会大大超过其好处:-)但是,至少从理论上讲,它可以实现完美的惰性日志记录。


1

我提出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)))))

如您所见,这还涵盖了使用lambda函数的其他答案,但是随着value属性和扩展的使用,将占用更多的内存。但是,它通过以下方式节省了更多内存:__slots__的用法?

最后,到目前为止,最有效的解决方案仍然是以下建议的另一个答案:

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

0

如果仅依赖于访问全局状态属性,则可以实例化一个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中的元类是什么?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.