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

105526 次浏览

您正在多次调用 Logger.myLogger()。请将它返回的日志记录器实例存储在某处并重用 那个

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

您的日志记录器应该作为单例程序工作。您不应该创建它多次。 下面是它可能看起来的样子:

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

对于给定的名称,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
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

Logger 的实现已经是一个单例模式。

对 logging.getLogger (‘ somLogger’)的多个调用返回一个引用 这不仅在相同的日志记录器对象中是正确的 模块,而且只要是在相同的 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
...

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

有关详细的答案,请参阅 这个问题。

双倍(或三倍)。.记录器输出也可能发生在你通过 importlib.reload重新加载你的模块时(出于同样的原因,在接受的答案中解释)。我添加这个答案只是为了将来的参考,因为它花了我一段时间来弄清楚为什么我的输出是重复(三重)。

一个简单的解决办法是

logger.handlers[:] = [handler]

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

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

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

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)

在上面的示例中,我们检查指定文件的处理程序是否已经与日志记录器挂钩,但是访问所有处理程序的列表使您能够决定是否应该添加另一个处理程序。

今天遇到了这个问题。由于我的函数是@staticmethod,所以上面的建议是用 Random ()解决的。

看起来像这样:

import random


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

自 Python 3.2以来,您只需检查处理程序是否已经存在,如果存在,则在添加新处理程序之前清除它们。当调试和代码包含日志记录器初始化时,这是非常方便的

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


logger.addHandler(handler)

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

为了解决这个问题,您可以在设置处理程序之前通过以下方式清除它们:

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

在大多数情况下,当发生这种情况时,每个模块只需要调用 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')

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

我已经将 logger作为 辛格尔顿使用,并检查了 if not len(logger.handlers),但是 还有副本: 它是格式化的输出,后面是未格式化的输出。

在我的情况下解决方案 : logger.propagate = False

学分到 这个答案医生

一个伐木工有三个操作员

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个旋转文件处理程序 而1个流处理程序 + 2个旋转文件处理程序(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

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

这个名称可能是一个以句点分隔的层次结构值,比如 foo.bar.baz (尽管它也可能只是一个普通的 foo)。层次结构列表中位置更低的日志记录器是列表中位置更高的日志记录器的子级。例如,给定一个名为 foo 的日志记录器,

名称为

foo.bar
foo.bar.baz


foo.bam

都是 < strong > foo 的后代。日志记录器名称层次结构类似于 Python 包层次结构,并且在组织时与它相同

使用推荐的结构对每个模块进行记录

logging.getLogger(__name__).

那是因为在模块里,

__name__

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

当我们在没有任何参数的情况下使用 getLogger ()时,它返回 RootLogger。

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

您可以通过在调用 getLogger ()时提供一个不同的 logger 名称来避免这种情况

logger1 = logging.getLogger("loggera")


logger2 = logging.getLogger("loggerb")

这招对我很管用。

问题在于数字处理程序,如果有多个处理程序,那么就有多个日志,所以在添加之前必须检查:

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