【问题标题】:Python logging module is printing lines multiple timesPython 日志记录模块多次打印行
【发布时间】:2013-07-19 12:18:48
【问题描述】:

我有以下代码:

import logging
class A(object):
    def __init__(self):
        self._l = self._get_logger()

    def _get_logger(self):
        loglevel = logging.INFO
        l = logging.getLogger(__name__)
        l.setLevel(logging.INFO)
        h = logging.StreamHandler()
        f = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        h.setFormatter(f)
        l.addHandler(h)
        l.setLevel(loglevel)
        return l  

    def p(self, msg):
        self._l.info(msg)

for msg in ["hey", "there"]:
    a = A()
    a.p(msg)

我得到的输出是:

2013-07-19 17:42:02,657 INFO hey
2013-07-19 17:42:02,657 INFO there
2013-07-19 17:42:02,657 INFO there

为什么“there”被打印两次?同样,如果我在循环中添加另一个 A 类对象并打印一条消息,它会被打印三次。

文档说,如果记录器的名称匹配,logging.getLogger() 将始终返回相同的记录器实例。在这种情况下,名称确实匹配。它不应该返回相同的记录器实例吗?如果确实如此,为什么会多次打印消息?

【问题讨论】:

    标签: python logging


    【解决方案1】:

    logger 只创建一次,但会创建多个处理程序。

    创建一次A

    a = A()
    for msg in ["hey", "there"]:
        a.p(msg)
    

    或更改_get_logger如下:

    def _get_logger(self):
        loglevel = logging.INFO
        l = logging.getLogger(__name__)
        if not getattr(l, 'handler_set', None):
            l.setLevel(loglevel)
            h = logging.StreamHandler()
            f = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            h.setFormatter(f)
            l.addHandler(h)
            l.setLevel(loglevel)
            l.handler_set = True
        return l  
    

    更新

    从 Python 3.2 开始,您可以使用 logging.Logger.hasHandlers 查看此记录器是否配置了任何处理程序。 (感谢@toom)

    def _get_logger(self):
        loglevel = logging.INFO
        l = logging.getLogger(__name__)
        if not l.hasHandlers():
            ...
        return l
    

    【讨论】:

    • 我的用例不同。我希望能够生成实现自己的记录器的类的多个对象。这是正确的做事方式吗?另外,为什么会多次打印日志行?
    • @NikhilSingh: getLogger 确实每次都返回相同的记录器,但是每次实例化A 时,都会在同一个记录器上调用addHandler。它将为您添加的每个处理程序打印一条消息。
    • 您正在为每个 A 实例构建一次记录器处理程序。您正在为“嘿”和“那里”创建一个 A 实例,因此有两个处理程序。将行更改为["hey", "there", "buddy"],您将获得 3 个“buddy”副本。每个类只创建一次 A._l 记录器,而不是每个实例一次。
    • 从python 3.2开始:你应该考虑l = logging.getLogger(__name__)然后l.hasHandlers()方法。做的正是工作! docs.python.org/3/library/…
    • @toom,感谢您提供的信息。我更新了答案以包含该信息。没注意方法。 +1
    【解决方案2】:

    在我的例子中,根记录器处理程序也被调用了,我所做的只是将记录器实例的propagate 属性设置为False

    import logging
    logger = logging.getLogger("MyLogger")
    
    # stop propagting to root logger
    logger.propagate = False
    
    # other log configuration stuff
    # ....
    

    【讨论】:

    • 这样,在使用相同内核时,您可以避免在每次运行脚本时多次打印相同的信息。
    【解决方案3】:

    从 python 3.2 及更新版本开始:

    考虑使用hasHandlers() 来检查记录器是否有处理程序。

    https://docs.python.org/3/library/logging.html#logging.Logger.hasHandlers

    【讨论】:

      【解决方案4】:

      最好在所有类和函数之外的模块级别设置记录器,以避免它重复设置处理程序。

      对于不可避免的用例,请检查已附加到指定记录器的处理程序的数量,或者最好还是检查列表中是否存在处理程序。在 Python 2.7.6 中,Logger 的类属性handlers 是在 Logger 类实例上设置的处理程序列表。不要附加列表中已经存在的处理程序。例如。

      >>> import logging
      >>> import logging.handlers # lib of log record handlers
      >>> dir(logging.handlers)   # all the handlers from the lib
      ['BaseRotatingHandler', 'BufferingHandler', 'DEFAULT_HTTP_LOGGING_PORT', 'DEFAULT_SOAP_LOGGING_PORT', 'DEFAULT_TCP_LOGGING_PORT', 'DEFAULT_UDP_LOGGING_PORT', 'DatagramHandler', 'HTTPHandler', 'MemoryHandler', 'NTEventLogHandler', 'RotatingFileHandler', 'SMTPHandler', 'ST_DEV', 'ST_INO', 'ST_MTIME', 'SYSLOG_TCP_PORT', 'SYSLOG_UDP_PORT', 'SocketHandler', 'SysLogHandler', 'TimedRotatingFileHandler', 'WatchedFileHandler', '_MIDNIGHT', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_unicode', 'cPickle', 'codecs', 'errno', 'logging', 'os', 're', 'socket', 'struct', 'time']
      >>> l = logging.getLogger()   # root logger
      >>> l.addHandler(logging.handlers.TimedRotatingFileHandler)
      >>> l.addHandler(logging.handlers.WatchedFileHandler)
      >>> l.handlers  # handlers set up on the logger instance
      [<class 'logging.handlers.TimedRotatingFileHandler'>, <class 'logging.handlers.WatchedFileHandler'>]
      >>> logging.handlers.TimedRotatingFileHandler in l.handlers # check - Yes
      True
      >>> logging.handlers.WatchedFileHandler in l.handlers # check - Yes
      True
      >>> logging.handlers.HTTPHandler in l.handlers # check - No
      False
      >>> 
      

      【讨论】:

        猜你喜欢
        • 2017-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-15
        • 2016-03-10
        • 1970-01-01
        • 2017-06-04
        相关资源
        最近更新 更多