我在我的 python 应用程序中使用标准 python 日志记录模块:
import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("log") while True: logger.debug('Stupid log message " + ' '.join([str(i) for i in range(20)]) ) # Do something
问题是,尽管调试级别未启用,但在每次循环迭代时都会评估该愚蠢的日志消息,这会严重损害性能。
有什么解决办法吗?
在 C++ 中,我们有 log4cxx
包,它提供如下宏:
LOG4CXX_DEBUG(logger, messasage)
这有效地计算为
if (log4cxx::debugEnabled(logger)) { log4cxx.log(logger,log4cxx::LOG4CXX_DEBUG, message) }
但是由于 Python (AFAIK) 中没有宏,是否有一种有效的方法来进行日志记录?
日志记录模块已经部分支持您想要做的事情。做这个:
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() 并没有那么远。
当然,以下内容不如宏有效:
if logger.isEnabledFor(logging.DEBUG):
logger.debug(
'Stupid log message ' + ' '.join([str(i) for i in range(20)])
)
但很简单,evaluates in lazy fashion 并且 比接受的答案快 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。
__str__
时从类中收集内容。所以基本上,我得到了几乎相同的结果。查看我的评论here
lazyjoin
实例所需的时间。另请参阅我对 Python: how to do lazy debug logging 的回答。
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 秒即可执行。这表明当日志记录级别低于设置级别时,不会评估参数。
正如 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())
在这种情况下,如果没有发生日志记录,even 将计算 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__
方法。
请注意:该解决方案的开销可能会大大超过收益 :-) 但至少在理论上,它可以进行完美的惰性日志记录。
我提出,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
属性和扩展的更多内存。但是,它通过以下方式节省更多内存:Usage of __slots__?
最后,到目前为止,最有效的解决方案仍然是以下建议的另一个答案:
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Stupid log message ' + ' '.join([str(i) for i in range(20)]))
如果您只依赖于访问全局状态属性,您可以实例化一个 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 )
有关的:
Python 中的条件评估调试语句 Python 中的元类是什么?