【问题标题】:Duplicate log output when using Python logging module使用 Python 日志记录模块时出现重复的日志输出
【发布时间】:2011-11-02 15:14:36
【问题描述】:

我正在使用 python 记录器。以下是我的代码:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

我遇到的问题是每个logger.info 调用的日志文件中都有多个条目。我该如何解决这个问题?

【问题讨论】:

  • 为我工作。 Python 3.2 和 Windows XP。
  • 您确定不创建多个记录器实例吗?
  • 是的。在不同的文件中,我正在采用新实例,就像我们在 Java 项目中所做的那样。请说明这是否造成问题。

标签: python


【解决方案1】:

问题是处理程序的数量,如果你有多个处理程序,你就有多个日志,所以你只需要在添加之前检查:

if not logger.handlers:
    logger.addHandler(handler)

【讨论】:

    【解决方案2】:

    当我们使用不带任何参数的 getLogger() 时,它会返回 RootLogger。

    因此,如果您在多个位置调用 getLogger() 并添加日志处理程序,它会将这些日志处理程序添加到 RootLogger(如果您不显式添加日志处理程序,它将自动添加 StreamHandler)。因此,当您尝试记录消息时,它将使用添加到 RootLogger 的所有处理程序记录消息。这就是重复日志的原因。

    您可以通过在调用 getLogger() 时提供不同的记录器名称来避免这种情况。类似的东西

    logger1 = logging.getLogger("loggera")
    
    logger2 = logging.getLogger("loggerb")
    

    这对我有用。

    【讨论】:

      【解决方案3】:

      logging.getLogger() 返回给定名称的相同实例。 (Documentation)

      问题是每次调用myLogger() 时,它都会向实例添加另一个处理程序,这会导致重复日志。

      也许是这样的?

      import os
      import time
      import datetime
      import logging
      
      loggers = {}
      
      def myLogger(name):
          global loggers
          
          if loggers.get(name):
              return loggers.get(name)
          else:
              logger = logging.getLogger(name)
              logger.setLevel(logging.DEBUG)
              now = datetime.datetime.now()
              handler = logging.FileHandler(
                  '/root/credentials/Logs/ProvisioningPython' 
                  + now.strftime("%Y-%m-%d") 
                  + '.log')
              formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
              handler.setFormatter(formatter)
              logger.addHandler(handler)
              loggers[name] = logger
                             
              return logger
      

      【讨论】:

      • 我认为你应该改用 loggers.update(dict((name,logger)))。
      • 为什么是loggers.update(dict(name=logger))loggers[name] = logger 不是更简单吗?
      • @RyanJMcCall 当时这是我使用的编码约定。但是在查看现在的代码时,我发现它已经损坏了。 loggers.update(dict(name=logger)) 将使用一个名为 name 的键创建一个字典,并不断更新同一个键。我很惊讶没有人之前没有提到这一点,因为这段代码很糟糕:) 将进行必要的修改。
      • 我看到@acrophobia 在很久以前就一直在逃避这个问题。谢谢。
      • 全局loggers 字典不是与logging.getLogger 冗余吗?因为您真的只是想避免添加额外的处理程序,所以您似乎更喜欢下面直接检查处理程序的答案
      【解决方案4】:

      我在一个记录器中有 3 个处理程序

      StreamHandler setLevel(args.logging_level)
      logging.FileHandler(logging.ERROR)
      RotatingFileHandler(args.logging_level)
      logger.setLevel(args.logging_level)
      

      我的代码在使用

      logger = logging.getLogger('same_name_everywhere')
      

      像这样产生重复的行和重复的处理程序,2 个流处理程序,3 个旋转文件处理程序 While 1 Stream Handler + 2 Rotating FileHanders (1 for errlog, 1 for generic log) 这是由

      完成的
      logger.warn(logger.handlers)
      cli_normalize_string: WARNING  [<StreamHandler <stderr> (DEBUG)>, <RotatingFileHandler /tmp/cli.normalize_string.py.2020-11-02.user.errlog (ERROR)>, <StreamHandler <stderr> (DEBUG)>, <RotatingFileHandler /tmp/cli.normalize_string.py.2020-11-02.user.log (DEBUG)>, <RotatingFileHandler /tmp/cli.normalize_string.py.2020-11-02.user.errlog (ERROR)>]
      

      我改成之后

      # The name is now become change.cli_normalize_string or change.normalize_string
      logger = logger.getLogger(__name__)
      

      在每个模块中,问题已解决,没有重复的行,1 个 StreamHeader,1 个用于错误日志记录的 FileHandler,1 个用于通用日志记录的 RotatingFileHandler

      2020-11-02 21:26:05,856 cli_normalize_string INFO     [<StreamHandler <stderr> (DEBUG)>, <FileHandler /tmp/cli.normalize_string.py.2020-11-02.user.errlog (ERROR)>, <RotatingFileHandler /tmp/cli.normalize_string.py.2020-11-02.user.log (DEBUG)>]
      

      详细信息在本文档中 https://docs.python.org/3/library/logging.html

      请注意,永远不要直接实例化 Logger,而应始终通过模块级函数 logging.getLogger(name) 实例化。多次调用具有相同名称的 getLogger() 将始终返回对同一 Logger 对象的引用。”

      名称可能是一个以句点分隔的分层值,例如 foo.bar.baz(尽管它也可能只是简单的 foo,例如)。层次列表中更靠下的记录器是列表中更高的记录器的子级。例如,给定一个名为 foo 的记录器,

      具有以下名称的记录器

      foo.bar
      foo.bar.baz
      
      foo.bam 
      

      都是foo的后代。记录器名称层次结构类似于 Python 包层次结构,如果您组织起来,则与它相同

      您的记录器在每个模块的基础上使用推荐的结构

      logging.getLogger(__name__). 
      

      那是因为在一个模块中,

      __name__ 
      

      是 Python 包命名空间中的模块名称。

      【讨论】:

        【解决方案5】:

        我已经将logger用作Singleton并检查了if not len(logger.handlers),但仍然有重复:这是格式化的输出,然后是未格式化的输出。

        解决方案就我而言: logger.propagate = False

        感谢this answerdocs

        【讨论】:

        • 我发现双倍的日志记录来自 RootLogger 和我的 StreamHandler,但在执行此操作之前无法解决问题(同时将我的格式化程序保留在 StreamHandler 上)。
        • 我遇到了类似的问题,logger 在调用logging.info() 后重复输出。通过仅通过我的logger 单例而不是 日志模块的info() 方法发出消息,我能够避免重复输出
        • 在我的格式化日志未格式化后(它是根日志),也有同样的问题。你的方法帮助我解决了这个问题。非常感谢。
        • @DenisOgr 乐于助人! :)
        • 终于找到了一个可行的解决方案!
        【解决方案6】:

        当这种情况发生时,大多数情况下,每个模块只需要调用 logger.getLogger() 一次。如果你像我一样有多个课程,我可以这样称呼它:

        LOGGER = logger.getLogger(__name__)
        
        class MyClass1:
            log = LOGGER
            def __init__(self):
                self.log.debug('class 1 initialized')
        
        class MyClass2:
            log = LOGGER
            def __init__(self):
                self.log.debug('class 2 initialized')
        

        然后,两者都将有自己的完整包名称和记录方法。

        【讨论】:

          【解决方案7】:

          这是对@rm957377 答案的补充,但解释了为什么会发生这种情况。当您在 AWS 中运行 lambda 函数时,它们会从一个包装实例中调用您的函数,该实例对多次调用保持活动状态。这意味着,如果您在函数代码中调用addHandler(),它将在每次函数运行时继续向日志单例添加重复的处理程序。 日志单例通过多次调用你的 lambda 函数持续存在。

          要解决这个问题,您可以在设置之前清除处理程序:

          logging.getLogger().handlers.clear()
          logging.getLogger().addHandler(...)
          

          【讨论】:

          • 不知何故,在我的情况下,记录器处理程序在.info() 调用的事件中被添加,我不明白。
          • handlers.clear() 在 python 2 中给了我一个错误,它说 clear 不是列表的方法。也许这是python 3中的一种方法?我这样做了:logging.getLogger().handlers = list()。似乎工作正常。
          【解决方案8】:

          从 Python 3.2 开始,您只需检查处理程序是否已经存在,如果存在,请在添加新处理程序之前清除它们。这在调试时非常方便,并且代码包括您的记录器初始化

          if (logger.hasHandlers()):
              logger.handlers.clear()
          
          logger.addHandler(handler)
          

          【讨论】:

          • 请注意 hasHandlers() 将在 pytest 中返回 true,其中处理程序已添加到根记录器,即使尚未添加本地/自定义处理程序也是如此。在这种情况下, len(logger.handlers) (根据纪尧姆的回答)将返回 0,因此可能是更好的选择。
          • 这是我正在寻找的真正解决方案。
          • 当现有处理程序直接属于我的记录器时,这有效,但如果处理程序来自我的记录器的父级,则无效。在这种情况下,只有logger.propagate = False 会删除下面@Mr 中提到的重复项。 B 的回答。有没有办法清除父处理程序?
          【解决方案9】:
          from logging.handlers import RotatingFileHandler
          import logging
          import datetime
          
          # stores all the existing loggers
          loggers = {}
          
          def get_logger(name):
          
              # if a logger exists, return that logger, else create a new one
              global loggers
              if name in loggers.keys():
                  return loggers[name]
              else:
                  logger = logging.getLogger(name)
                  logger.setLevel(logging.DEBUG)
                  now = datetime.datetime.now()
                  handler = logging.FileHandler(
                      'path_of_your_log_file' 
                      + now.strftime("%Y-%m-%d") 
                      + '.log')
                  formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
                  handler.setFormatter(formatter)
                  logger.addHandler(handler)
                  loggers.update(dict(name=logger))
                  return logger
          

          【讨论】:

          • 请添加解释以使此答案对长期使用更有价值。
          【解决方案10】:

          今天遇到了这个问题。由于我的函数是@staticmethod,因此上述建议已通过 random() 解决。

          看起来像:

          import random
          
          logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))
          

          【讨论】:

            【解决方案11】:

            您可以获取特定记录器的所有处理程序的列表,因此您可以执行类似的操作

            logger = logging.getLogger(logger_name)
            handler_installed = False
            for handler in logger:
                # Here your condition to check for handler presence
                if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
                    handler_installed = True
                    break
            
            if not handler_installed:
                logger.addHandler(your_handler)
            

            在上面的示例中,我们检查指定文件的处理程序是否已经连接到记录器,但是访问所有处理程序的列表使您能够决定应该根据哪些条件添加另一个处理程序。

            【讨论】:

              【解决方案12】:

              一个简单的解决方法就像

              logger.handlers[:] = [handler]
              

              这样可以避免将新的处理程序附加到基础列表“处理程序”中。

              【讨论】:

                【解决方案13】:

                当您通过importlib.reload 重新加载模块时,也可能会出现双倍(或三倍或..- 基于重新加载次数)记录器输出(原因与已接受答案中解释的相同)。我添加这个答案只是为了将来参考,因为我花了一段时间才弄清楚为什么我的输出是重复的(三重)。

                【讨论】:

                  【解决方案14】:

                  logger 的实现已经是单例了。

                  多次调用 logging.getLogger('someLogger') 返回一个引用 到同一个记录器对象。这不仅在同一 模块,但也可以跨模块,只要它在同一个 Python 中 解释器过程。对同一个对象的引用是正确的; 此外,应用程序代码可以定义和配置父级 在一个模块中记录并在其中创建(但不配置)一个子记录器 一个单独的模块,所有对孩子的记录器调用都将传递给 父母。这是一个主模块

                  来源-Using logging in multiple modules

                  所以你应该使用它的方式是 -

                  假设我们在主模块中创建并配置了一个名为 '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
                  ...
                  

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

                  请参阅this 问题以获得更详细的答案。

                  【讨论】:

                  • 在 getLogger 之后重新定义处理程序似乎对我有用:logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
                  • 这是一个比其他人更好的答案IMO
                  【解决方案15】:
                  import datetime
                  import logging
                  class Logger :
                      def myLogger(self):
                         logger=logging.getLogger('ProvisioningPython')
                         if not len(logger.handlers):
                            logger.setLevel(logging.DEBUG)
                            now = datetime.datetime.now()
                            handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
                            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
                            handler.setFormatter(formatter)
                            logger.addHandler(handler)
                          return logger
                  

                  帮我搞定了

                  使用 python 2.7

                  【讨论】:

                  • 即使重新加载模块也有效(其他答案不是这种情况)
                  • 感谢您的提示,顺便说一句,要检查列表是否为空,您不需要使用“len”运算符,您可以直接使用 if my_list: ..
                  【解决方案16】:

                  您的记录器应该作为单例工作。您不应该多次创建它。 以下是它的外观示例:

                  import os
                  import time
                  import datetime
                  import logging
                  class Logger :
                      logger = None
                      def myLogger(self):
                          if None == self.logger:
                              self.logger=logging.getLogger('ProvisioningPython')
                              self.logger.setLevel(logging.DEBUG)
                              now = datetime.datetime.now()
                              handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
                              formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
                              handler.setFormatter(formatter)
                              self.logger.addHandler(handler)
                          return self.logger
                  
                  s = Logger()
                  m = s.myLogger()
                  m2 = s.myLogger()
                  m.info("Info1")
                  m2.info("info2")
                  

                  【讨论】:

                  • 如果我要在不同的文件中使用不同的实例,那么再次。假设在文件 1 s = Logger() m = s.myLogger() 和文件 2 s = Logger() 它将工作或不工作 m2 = s.myLogger()
                  • 我仍然多次获得相同日志的副本。我在这里怀疑内部线程日志是否打印多个。请帮助我。
                  • @user865438,我们不需要担心使实现成为单例(它已经是)。如需登录子模块,请遵循官方 Logging Cookbook link。基本上,您需要在命名记录器时遵循命名层次结构,其余部分由它负责。
                  【解决方案17】:

                  您不止一次致电Logger.myLogger()。将它返回的记录器实例存储在某处并重用那个

                  另外请注意,如果您在添加任何处理程序之前登录,则会创建默认的 StreamHandler(sys.stderr)

                  【讨论】:

                  • 实际上我正在尝试访问我们在java中使用的记录器实例。但我不知道是否需要为整个项目只创建一次实例。
                  • @user865483:只有一次。所有标准库记录器都是单例的。
                  猜你喜欢
                  • 2020-11-03
                  • 1970-01-01
                  • 2015-04-05
                  • 2011-05-09
                  • 1970-01-01
                  • 2011-10-07
                  • 1970-01-01
                  • 2020-08-11
                  • 2014-10-21
                  相关资源
                  最近更新 更多