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

时间:2011-08-24 09:14:18

标签: python

我正在使用python logger。以下是我的代码:

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调用的日志文件中都有多个条目。我该如何解决这个问题?

15 个答案:

答案 0 :(得分:75)

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

答案 1 :(得分:39)

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

答案 2 :(得分:34)

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

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

logger.addHandler(handler)

答案 3 :(得分:9)

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

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

答案 4 :(得分:4)

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问题以获得更详细的答案。

答案 5 :(得分:3)

您的记录器应该像单身人士一样工作。你不应该多次创建它。 以下是它的外观示例:

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")

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

当您通过importlib.reload重新加载模块时,也可能发生双重(或三重或...... - 基于重新加载次数)记录器输出(原因与接受的答案中说明的相同)。我正在添加这个答案仅供将来参考,因为我花了一些时间来弄清楚为什么我的输出是dupli(三重)装箱。

答案 8 :(得分:1)

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

解决方案logger.propagate = False

贷记this answerdocs

答案 9 :(得分:0)

一个简单的解决方法就像

logger.handlers[:] = [handler]

这样就可以避免将新处理程序附加到基础列表"处理程序"。

答案 10 :(得分: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)

在上面的例子中,我们检查指定文件的处理程序是否已经挂钩到记录器,但是有权访问所有处理程序的列表,这使您能够决定应该添加另一个处理程序的标准。 / p>

答案 11 :(得分:0)

今天有这个问题。由于我的函数是@staticmethod,上面的建议是用random()解决的。

看起来像:

import random

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

答案 12 :(得分:0)

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

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

答案 13 :(得分:0)

我在一个记录器中有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个旋转FileHanders 1个流处理程序+ 2个旋转FileHanders(1个用于errlog,1个用于通用日志) 这是通过

完成的
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包命名空间中模块的名称。

答案 14 :(得分:-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