使用Python日志记录模块时重复的日志输出


105

我正在使用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。
Zuljin

2
您确定不创建多个记录器实例吗?
甘迪

是。在不同的文件中,我将像在Java项目中一样使用新实例。请指定我是否正在造成问题。
user865438 2011年

Answers:


94

logging.getLogger()已经是一个单例。(资料

问题在于,每次调用时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

3
我认为您应该改用loggers.update(dict((name,logger)))。
恐高症2012年

为什么loggers.update(dict(name=logger))呢?是不是loggers[name] = logger更简单?
瑞安·麦考

@RyanJMcCall当时是我使用的编码约定。但是,按现在的方式检查代码后,我发现它已损坏。loggers.update(dict(name=logger))将创建一个具有单个键的字典,name并不断更新该键。令我惊讶的是,由于该代码已被破坏,因此没有人更早提及:)将进行必要的修改。
Werner Smit

我看到@acrophobia在这个时代之前就已经不懂了。谢谢。
Werner Smit

全局loggers字典不是多余的logging.getLogger吗?由于您实际上只是想避免添加额外的处理程序,因此您似乎更喜欢直接在下面检查处理程序的答案
mway

60

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

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

logger.addHandler(handler)

好答案,谢谢:))
加夫里耶尔·科恩

2
请注意,即使尚未添加本地/自定义处理程序,在已将处理程序添加到根记录器的pytest中,hasHandlers()也会返回true。在这种情况下,len(logger.handlers)(根据Guillaume的回答)将返回0,因此可能是一个更好的选择。
格兰特(Grant)

这是我一直在寻找的真正解决方案。
XCanG

45
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


1
即使重新加载模块,此方法仍然有效(其他答案不是这种情况)
yco

3
感谢您的提示,BTW检查列表是否为空,您不需要使用“ len”运算符,如果my_list:..
rkachach '17

26

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

我的解决方案logger.propagate = False

归功于这个答案文档


1
我已经知道翻倍的日志记录来自RootLogger和我的StreamHandler,但是直到做到这一点(同时将格式化程序保留在StreamHandler上)无法解决该问题。
Xander YzWich

10

您拨打Logger.myLogger()了不止一次。存储返回某处记录器实例和重用

还建议如果在添加任何处理程序之前登录,StreamHandler(sys.stderr)将创建默认值。


实际上我正在尝试访问Java中使用的记录器实例,但是我不知道是否需要为整个项目创建一个实例一次。
user865438 2011年

1
@ user865483:一次。所有标准库记录器都是单例。
马特·乔纳

5

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

为了解决这个问题,您可以在设置处理器之前先清除它们,方法是:

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

就我而言,记录器处理程序会以某种方式在我.info()不了解的通话事件中添加。
Evgeny

4

您的记录器应作为单例工作。您不应该多次创建它。这是看起来的示例:

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 2011年

我仍然多次获得同一日志的副本。我在这里怀疑Log Log内的线程是否打印多个。请对此提供帮助。
user865438 2011年

1
@ user865438,我们不必担心使实现成为单例(它已经是)。要登录子模块,请遵循官方的Logging Cookbook 链接。基本上,在命名记录器时,您需要遵循命名层次结构,其余的工作都由它来完成。
narayan 2015年

4

logger的实现已经是单例。

多次调用logging.getLogger('someLogger')会返回对同一记录器对象的引用。只要在同一Python解释器进程中,不仅在同一模块内,而且在各个模块之间都是如此。对于相同对象的引用是正确的。此外,应用程序代码可以在一个模块中定义和配置父记录器,并在单独的模块中创建(但不能配置)子记录器,并且所有对子记录器的调用都将传递给父记录器。这是一个主要模块

来源- 在多个模块中使用日志记录

因此,您应该利用它的方式是-

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

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

对一个小更详细的回答问题。


1
在getLogger之后重新定义处理程序似乎对我logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
有用

2

当您通过方式重新加载模块时,也会发生两倍(或三倍或..-基于重新加载的次数)记录器输出importlib.reload(出于与接受的答案中所述相同的原因)。我添加此答案只是为了将来参考,因为花了我一段时间才弄清楚为什么我的输出是dupli(triple)cated。


1

一种简单的解决方法是

logger.handlers[:] = [handler]

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


1

在大多数情况下,这种情况的底线是,每个模块只需要调用一次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')

然后两者都将在记录的地方拥有自己的完整程序包名称和方法。


0

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

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)

在上面的示例中,我们检查指定文件的处理程序是否已经连接到记录器,但是可以访问所有处理程序的列表,使您能够决定应添加哪个处理程序或不添加其他处理程序。


0

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

看起来像:

import random

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

-1
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

请添加说明以使该答案对于长期使用更有价值。
Aminah Nuraini '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.