【问题标题】:python logging ensure a handler is added only oncepython logging 确保只添加一次处理程序
【发布时间】:2011-09-14 02:29:39
【问题描述】:

我有一段代码正在初始化一个记录器,如下所示。

logger = logging.getLogger()
hdlr = logging.FileHandler('logfile.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr) 
logger.setLevel(logging.DEBUG)

不幸的是,这段代码被多次调用,有什么方法可以检查处理程序是否已经存在 - 我更愿意在不必使用 Singleton 的情况下实现它。

编辑:抱歉,忘了提到这是在 python 2.5 上 - 干杯,理查德

【问题讨论】:

  • 我认为您应该重新评估答案,因为 mouad 的答案(在撰写本文时)忽略了这样一个事实,即多次调用返回相同记录器对象的方法 CAN 添加重复的处理程序. narayan 很好地解释了这一点。

标签: python logging


【解决方案1】:

如果处理程序已经存在,logger.addHandler() 将不会添加处理程序。要检查处理程序是否已经存在,您可以检查 logger.handlers 列表:

logger = logging.getLogger()
hdlr = logging.FileHandler('logfile.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr) 
logger.setLevel(logging.DEBUG)
print logger.handlers
# [<logging.FileHandler object at 0x14542d0>]
logger.addHandler(hdlr)
print logger.handlers
# [<logging.FileHandler object at 0x14542d0>]

除此之外,如果您有此代码,我建议将此代码放入您的 main() 函数中,或者放入您的包的 __init__.py 文件中,这样就不必每次都调用它。我还建议您使用命名记录器,不要使用根记录器。像这样的:

logger = logging.getLogger(__name__)
...

希望这会有所帮助:)

【讨论】:

  • “Logger.handlers”列表是否记录在任何地方?我在docs.python.org/2/library/logging.html 上找不到它
  • @MariuszPluciński:是的,我认为它没有记录在任何地方,但如果我记得清楚,我必须检查 Logger 类代码以了解它是如何工作的:hg.python.org/cpython/file/482590320549/Lib/logging/…
  • 但是如果它没有被记录,那么它可能会在未来版本的日志库中悄然消失。我不确定依赖此功能是否好(但我同意,似乎没有任何记录在案的方法可以检查此功能)。
  • 这并不完全正确,因为可以多次添加相同的 type 处理程序:[我知道评论格式令人毛骨悚然]>>> log = logging.getLogger() >>> hdlr = logging.StreamHandler() >>> hndl = logging.StreamHandler() >>> hdlr >>> hndl > >> log.addHandler(hdlr) >>> log.addHandler(hndl) >>> log.handlers [, ]
【解决方案2】:

尝试检查logger 是否已设置。例如,如果这段代码在函数内部:

logger = None
def init_logger():
    global logger
    if logger is not None:
        #logger has already been initialized
        return
    logger = logging.getLogger()
    hdlr = logging.FileHandler('logfile.log')
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    hdlr.setFormatter(formatter)
    logger.addHandler(hdlr) 
    logger.setLevel(logging.DEBUG)

【讨论】:

  • 我猜检查 if logger is not None 不是线程安全的,所以不能保证初始化代码不会运行超过一次。
【解决方案3】:

您也可以只检查处理程序列表是否为空。这是我最终得到的解决方案:

def setup_logging(self, logfile):
    self._logger = logging.getLogger('TestSuite')
    self._logger.setLevel(logging.INFO)
    host = socket.gethostname().split('.')[0]
    if self._logger.handlers == []:
        fh = logging.handlers.RotatingFileHandler(logfile,
                                                  maxBytes=10*1024*1024,
                                                  backupCount=5)
        strfmt = "%" + "(asctime)s [%s] " % host + "%" + "(message)s"
        fmt = logging.Formatter(strfmt, datefmt="%Y.%m%d %H:%M:%S")
        fh.setFormatter(fmt)

        self._logger.addHandler(fh)
    self._logger.info('-' * (55 - len(host)))

我看到处理程序添加了多次,因此每条日志消息都被多次写入日志文件,这解决了它。

【讨论】:

    【解决方案4】:

    作为@offbyone cmets,可以将冗余处理程序添加到记录器的同一实例。 python docs for logging 说-

    "多次调用同名的getLogger()会返回一个 引用同一个记录器对象。”

    所以我们不必担心将实现变成单例,因为它已经是。

    不幸的是,与同一记录器实例关联的处理程序不正确可能附加了重复的处理程序。

    例子-

    1. 复制这段代码并保存在main.py中

      import logging
      print 'inside main.py',
      print '-'*50
      def logger():
      
            print 'initializing logger....'
            logPath = '.'
            fileName = 'temp'
      
            # configure log formatter
            logFormatter = logging.Formatter("%(asctime)s [%(filename)s] [%(funcName)s] [%(levelname)s] [%(lineno)d] %(message)s")
      
            # configure file handler
            fileHandler = logging.FileHandler("{0}/{1}.log".format(logPath, fileName))
            fileHandler.setFormatter(logFormatter)
      
            # configure stream handler
            consoleHandler = logging.StreamHandler()
            consoleHandler.setFormatter(logFormatter)
      
            # get the logger instance
            logger = logging.getLogger(__name__)
      
            # set the logging level
            logger.setLevel(logging.DEBUG)
      
            print 'adding handlers- '
      
            #if not len(logger.handlers):
            logger.addHandler(fileHandler)
            logger.addHandler(consoleHandler)
      
            print 'logger initialized....\n'
            print 'associated handlers - ', len(logger.handlers)
            for handler in logger.handlers:
                  print handler
            print
            return logger
      
      main_logger = logger()
      main_logger.info('utilizing main.py logger.')
      print 'exiting main.py',
      print '-'*50
      
    2. 以及sub.py中的以下代码

      print 'inside sub.py',
      print '-'*50
      print 'importing main.py'
      import main
      print 'imported main.py'
      import logging
      print 'getting logger instance in sub'
      sub_logger = main.logger()
      print 'got logger instance in sub'
      sub_logger.info("utilizing sub_logger")
      print 'exiting sub.py',
      print '-'*50
      
    3. 运行 sub.py

      narayan@y510p:~/code/so$ python sub.py
      inside sub.py --------------------------------------------------
      importing main.py
      inside main.py --------------------------------------------------
      initializing logger....
      adding handlers- 
      logger initialized....
      
      associated handlers -  2
      <logging.FileHandler object at 0x7f7158740c90>
      <logging.StreamHandler object at 0x7f7158710b10>
      
      2015-08-04 07:41:01,824 [main.py] [<module>] [INFO] [41] utilizing main.py logger.
      exiting main.py --------------------------------------------------
      imported main.py
      getting logger instance in sub
      initializing logger....
      adding handlers- 
      logger initialized....
      
      associated handlers -  4 # <===== 4 handlers (duplicates added)
      <logging.FileHandler object at 0x7f7158740c90>
      <logging.StreamHandler object at 0x7f7158710b10>
      <logging.FileHandler object at 0x7f7158710bd0>
      <logging.StreamHandler object at 0x7f7158710c10>
      
      got logger instance in sub
      2015-08-04 07:41:01,824 [sub.py] [<module>] [INFO] [10] utilizing sub_logger
      2015-08-04 07:41:01,824 [sub.py] [<module>] [INFO] [10] utilizing sub_logger
      exiting sub.py --------------------------------------------------
      

    因此,多次调用返回相同记录器的方法会添加重复的处理程序。

    现在,你的问题-

    有什么方法可以检查处理程序是否已经存在

    是的,有-

    logger.handlers 返回与给定logger 关联的所有处理程序的列表。

    在将处理程序添加到记录器的实例之前,请确保不要添加重复的处理程序 在 main.py 中,取消注释 if not len(logger.handlers): 并正确缩进以下两行 -

    if not len(logger.handlers):
        logger.addHandler(fileHandler)
        logger.addHandler(consoleHandler)
    

    现在再次运行 sub.py

    narayan@y510p:~/code/so$ python sub.py
    inside sub.py --------------------------------------------------
    importing main.py
    inside main.py --------------------------------------------------
    initializing logger....
    adding handlers- 
    logger initialized....
    
    associated handlers -  2
    <logging.FileHandler object at 0x7fd67a891c90>
    <logging.StreamHandler object at 0x7fd67a862b10>
    
    2015-08-04 08:14:45,620 [main.py] [<module>] [INFO] [41] utilizing main.py logger.
    exiting main.py --------------------------------------------------
    imported main.py
    getting logger instance in sub
    initializing logger....
    adding handlers- 
    logger initialized....
    
    associated handlers -  2 # <===== Still 2 handlers (no duplicates)
    <logging.FileHandler object at 0x7fd67a891c90>
    <logging.StreamHandler object at 0x7fd67a862b10>
    
    got logger instance in sub
    2015-08-04 08:14:45,620 [sub.py] [<module>] [INFO] [10] utilizing sub_logger
    exiting sub.py --------------------------------------------------
    

    此外,如果您想限制要添加到记录器实例的处理程序的类型,您可以执行以下操作-

        print 'adding handlers- '
        # allows to add only one instance of file handler and stream handler
        if len(logger.handlers) > 0:
            print 'making sure we do not add duplicate handlers'
            for handler in logger.handlers:
                  # add the handlers to the logger
                  # makes sure no duplicate handlers are added
    
                  if not isinstance(handler, logging.FileHandler) and not isinstance(handler, logging.StreamHandler):
                        logger.addHandler(fileHandler)
                        print 'added file handler'
                        logger.addHandler(consoleHandler)
                        print 'added stream handler'
        else:
            logger.addHandler(fileHandler)
            logger.addHandler(consoleHandler)
            print 'added handlers for the first time'
    

    希望这会有所帮助!

    编辑:

    不幸的是,对于关联的处理程序,不正确 与记录器的相同实例。 可以重复 附加处理程序。

    事实证明,上述说法并不完全正确。

    假设我们在主模块中创建并配置了一个名为 'main_logger' 的记录器(它只是配置记录器,不返回任何内容)。

    # get the logger instance
    logger = logging.getLogger("main_logger")
    # configuration follows
    ...
    

    现在在子模块中,如果我们按照命名层次'main_logger.sub_module_logger'创建一个子记录器,我们不需要在子模块中配置它。只需按照命名层次结构创建记录器就足够了。

    # get the logger instance
    logger = logging.getLogger("main_logger.sub_module_logger")
    # no configuration needed
    # it inherits the configuration from the parent logger
    ...
    

    它也不会添加重复的处理程序。

    参考-Using logging in multiple modules

    【讨论】:

    • 这绝对是最好的答案。不幸的是,mouad 的回答是短视的,因为它错过了记录器的单个实例(单例)的重复处理程序(不是单例)的可能性。您的回答帮助我解决了我的问题。
    • "logger.handlers 返回与给定记录器关联的所有处理程序的列表" ...是的,但我能问你是如何找到这个的吗?你回到源代码了吗?因为 python 日志记录模块文档docs.python.org/2/library/logging.html 没有显示此信息。有时我觉得 Python 的标准文档有点欠缺……有什么更好的推荐吗?
    • @mikerodent 我在 SO 上发现了关于 logger.handlers 的信息,当时我正在努力解决日志记录中的重复输出问题,并在源代码中验证了这一点。关于文档,通常情况下,标准文档可以完成这项工作。 pymotw 在我想要借助 python 标准库完成特定的事情时帮助我。
    • 非常感谢,您(顺便说一句)的回答对我帮助很大。
    • 我使用的是 Python 2.7.12,logging-0.5.1.2。我在我的主模块中初始化一个记录器并添加一个StreamHandler。在另一个模块中,然后我使用logger2 = logging.getLogger(__name__) 创建另一个记录器实例。然后logger2.handlers 返回[]。我需要做logger2.root.handlers 来检查处理程序。仅供参考。
    【解决方案5】:

    如果您熟悉 AWS Lambda,那么您可能已经知道在某些情况下,处理程序是预先配置的 [1]。假设logger.handlers 不为空,是不够的。我建议在记录器实例上设置一个属性,如下所示:

    def init_logger(logger):
        if hasattr(logger, 'initialized'):
            return logger  # No need for addHandler
        else:
            setattr(logger, 'initialized', True)
    
        # Initialize the logger
        # ...
    

    [1]Using python Logging with AWS Lambda

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-11
      • 2017-10-05
      • 2011-03-07
      • 2012-09-15
      • 1970-01-01
      • 2011-01-02
      相关资源
      最近更新 更多