【问题标题】:Lazy logger message string evaluation惰性记录器消息字符串评估
【发布时间】:2011-05-08 02:53:57
【问题描述】:

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

导入日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger("log") 而真: logger.debug('愚蠢的日志消息 " + ' '.join([str(i) for i in range(20)]) ) # 做一点事

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

有什么解决办法吗?

在 C++ 中,我们有 log4cxx 包,它提供如下宏:
LOG4CXX_DEBUG(logger, messasage)
这有效地评估为

if (log4cxx::debugEnabled(logger)) { log4cxx.log(记录器,log4cxx::LOG4CXX_DEBUG,消息) }

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

【问题讨论】:

    标签: python logging


    【解决方案1】:

    如果你只依赖于访问全局状态属性,你可以实例化一个 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. Conditionally evaluated debug statements in Python
    2. What are metaclasses in Python?

    【讨论】:

      【解决方案2】:

      我在场,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)]))
      

      【讨论】:

        【解决方案3】:

        当然下面的效率不如宏:

        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
        • @guyarad:您也需要花时间创建lazyjoin 实例。另请参阅我对Python: how to do lazy debug logging 的回答。
        • @schnittstabil 不一定。你可以查看我的full description。就我而言,我不需要参数,只需要一个方法调用。所以我可以创建一次惰性对象,然后传递对象本身(而不创建它)。它使事情变得比“如果”建议稍微糟糕。你可以根据你的要点检查我的gist我。
        【解决方案4】:

        正如 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__ 方法。

        请注意:该解决方案的开销可能远远超过收益 :-) 但至少在理论上,它可以进行完美的惰性日志记录。

        【讨论】:

          【解决方案5】:

          日志记录模块已经部分支持您想要执行的操作。这样做:

          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-strings怎么样?它不会懒惰(我测试过)
          • 对于 f 字符串,请参阅stackoverflow.com/a/49884004/1783801,这也应该适用于此处。
          【解决方案6】:
          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 秒即可执行。这表明当日志记录级别低于设置的级别时,不会评估参数。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-02-08
            • 1970-01-01
            • 2013-03-11
            • 2017-01-18
            • 2018-11-11
            • 2017-12-12
            • 2015-05-29
            相关资源
            最近更新 更多