【问题标题】:Python - Avoid passing logger reference between functions?Python - 避免在函数之间传递记录器引用?
【发布时间】:2011-08-23 21:00:00
【问题描述】:

我有一个使用内置 logging 的简单 Python 脚本。

我正在函数内部配置日志记录。基本结构是这样的:

#!/usr/bin/env python
import logging
import ...

def configure_logging():
    logger = logging.getLogger("my logger")
    logger.setLevel(logging.DEBUG)
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    return logger

def count_parrots():
    ...
    logger.debug??

if __name__ == '__main__':
    logger = configure_logging()
    logger.debug("I'm a log file")
    parrots = count_parrots()

我可以从__main__ 内部调用 logger。但是,如何从 count_parrots() 函数内部调用 logger?像这样处理配置记录器的最 Pythonic 方式是什么?

【问题讨论】:

    标签: python logging


    【解决方案1】:

    您既可以使用根(默认)记录器,也可以使用模块级函数logging.debug, ... 或者在使用它的函数中获取记录器。 事实上,getLogger 函数是一个类似工厂的函数,带有一个注册表(类似单例),即它总是为给定的记录器名称返回相同的实例。 因此,您只需使用

    即可在 count_parrots 中获取您的记录器
    logger = logging.getLogger("my logger") 
    

    在开头。但是,约定是为您的记录器使用带点的分层名称。见http://docs.python.org/library/logging.html#logging.getLogger

    编辑:

    您可以使用装饰器将日志记录行为添加到您的各个函数中,例如:

    def debug(loggername):
        logger = logging.getLogger(loggername) 
        def log_(enter_message, exit_message=None):
            def wrapper(f):
                def wrapped(*args, **kargs):
                    logger.debug(enter_message)
                    r = f(*args, **kargs)
                    if exit_message:
                        logger.debug(exit_message)
                    return r
                return wrapped
            return wrapper
        return log_
    
    my_debug = debug('my.logger')
    
    @my_debug('enter foo', 'exit foo')
    def foo(a, b):
        return a+b
    

    您可以“硬编码”记录器名称并删除顶级闭包和 my_debug。

    【讨论】:

    • 好的,所以我可以在每个需要记录的函数的开头调用 logging.getLogger。似乎有点浪费和重复?很公平。还是我会更好地面向对象,并尝试将所有内容硬塞到一个类中? (我知道这是一个非常笼统的问题,我只是在寻找 Python 世界中所做的事情)。
    • 你可以把你的函数放在一个类中,记录器作为实例变量,或者(我更喜欢)创建一个装饰器来为你的单个函数添加记录功能
    • 这个答案几乎显示了 Python logging 模块的所有问题......
    • 我很好奇人们认为这种横切模块需要哪些替代方案?从通用设计的角度来看,我不知道 logging.getLogger 有什么问题。看起来很样板的东西。
    【解决方案2】:

    你可以这样做:

    logger = logging.getLogger("my logger") 
    

    在您的 count_parrots() 方法中。当您传递之前使用的名称(即“我的记录器”)时,日志记录模块将返回与该名称对应的相同实例。

    更新:来自logging tutorial (强调我的)

    getLogger() 返回对 a 的引用 具有指定的记录器实例 如果提供,则为名称,如果提供,则为 root 不是。名称以句点分隔 层次结构。 多个 以相同的方式调用 getLogger() name 将返回对 相同的记录器对象。

    【讨论】:

      【解决方案3】:

      处理日志的典型方法是将每个模块的记录器存储在全局变量中。该模块中的任何函数和方法都只是引用同一个记录器实例。

      这在文档中的高级日志教程简介中进行了简要讨论: http://docs.python.org/howto/logging.html#advanced-logging-tutorial

      可以将记录器实例作为参数传递,但这样做通常很少见。

      【讨论】:

      • 我认为标准做法是使用 logger=logging.getLogger("logger.name")
      • 在模块级别,当然。不过,在同一个模块中为不同的功能和方法使用单独的记录器通常是矫枉过正的。一个例外是使用单独的记录器可能是一种非常简单的方法来记录哪些线程正在记录特定事件。
      • 啊。我以为你的意思是实际使用 global 关键字。
      • @MatthewCornell 你的意思是logger = logging.getLogger(__name__)
      【解决方案4】:

      我对全局变量在 Python 中的工作方式感到困惑。在函数中,如果您正在执行类似logger = logging.getLogger("my logger") 的操作并希望修改全局logger,则只需声明global logger

      因此,要修改您的示例,您可以在文件开头创建一个全局记录器对象。如果你的模块可以被另一个模块导入,你应该添加NullHandler,这样如果库的导入器不想启用日志记录,他们就不会对你的库有任何问题(ref)。

      #!/usr/bin/env python
      import logging
      import ...
      
      logger = logging.getLogger("my logger").addHandler(logging.NullHandler())
      
      def configure_logging():
          logger.setLevel(logging.DEBUG)
          # Format for our loglines
          formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
          # Setup console logging
          ch = logging.StreamHandler()
          ch.setLevel(logging.DEBUG)
          ch.setFormatter(formatter)
          logger.addHandler(ch)
          # Setup file logging as well
          fh = logging.FileHandler(LOG_FILENAME)
          fh.setLevel(logging.DEBUG)
          fh.setFormatter(formatter)
          logger.addHandler(fh)
      
      def count_parrots():
          ...
          logger.debug('counting parrots')
          ...
          return parrots
      
      if __name__ == '__main__':
          configure_logging()
          logger.debug("I'm a log file")
          parrots = count_parrots()
      

      【讨论】:

        【解决方案5】:

        如果您不需要控制台上的日志消息,您可以以极简的方式使用。

        您也可以使用tail -f myapp.log 在控制台上查看消息。

        import logging
        
        logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', \
            filename='myapp.log', \
            level=logging.INFO)
        
        def do_something():
            logging.info('Doing something')
        
        def main():
            logging.info('Started')
            do_something()
            logging.info('Finished')
        
        if __name__ == '__main__':
            main()
        

        【讨论】:

          【解决方案6】:

          您可以将logger 作为count_parrots() 的参数,或者,我会做的是,创建类鹦鹉并使用记录器作为其方法之一。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-04-09
            • 2013-03-07
            • 1970-01-01
            相关资源
            最近更新 更多