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

2025-03-18 08:54:00
admin
原创
41
摘要:问题描述:我正在使用 python 记录器。以下是我的代码:import os import time import datetime import logging class Logger : def myLogger(self): logger = logging.getLogger('...

问题描述:

我正在使用 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调用时日志文件中都会出现多个条目。我该如何解决这个问题?


解决方案 1:

函数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

解决方案 2:

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

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

logger.addHandler(handler)

解决方案 3:

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

就我的情况来说,解决方案
logger.propagate = False

感谢这个答案和文档。

解决方案 4:

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

解决方案 5:

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

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

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

解决方案 6:

您调用了Logger.myLogger()多次。将其返回的记录器实例存储在某处并重新使用

还请注意,如果在添加任何处理程序之前进行登录,StreamHandler(sys.stderr)则会创建一个默认值。

解决方案 7:

问题在于处理程序的数量,如果您有多个处理程序,那么您就有多个日志,因此您只需在添加之前检查:

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

解决方案 8:

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

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

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

解决方案 9:

您的记录器应以单例形式工作。您不应多次创建它。以下是它可能的样子:

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

解决方案 10:

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

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

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

logger1 = logging.getLogger("loggera")

logger2 = logging.getLogger("loggerb")

这对我很有用。

解决方案 11:

一个简单的解决方法是

logger.handlers[:] = [handler]

这样,您就避免将新的处理程序附加到底层列表“处理程序”中。

解决方案 12:

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

解决方案 13:

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

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

解决方案 14:

让我补充一下造成我痛苦的原因。

简而言之,在实用程序模块中,我logging仅导入了类型提示。并且我从调用程序模块传入了记录器,以确保我们正在使用记录器对象,如示例代码中所示。

但是我的日志有重复的行,一个格式与我的记录器对象中设置的格式相同,而另一个重复的行未格式化,内容完全相同。


import logging

import paramiko

# make paramiko module logging less chatty 
logging.getLogger("paramiko").setLevel(logging.WARNING)


def validate_s3_prefix_string(s3_bucket_prefix_: str, logger_: logging.Logger, log_level_: str = 'warning') -> str:
    if not s3_bucket_prefix_.endswith('/'):
        message = f"Invalid S3 bucket prefix: {s3_bucket_prefix_}. It should end with '/'"
        if log_level_ == 'warning':
            logger_.warning(message)
        elif log_level_ == 'error':
            logger_.error(message)
            raise ValueError(message)
    else:
        logging.info(f"The S3 bucket prefix seems to be in the correct format: {s3_bucket_prefix_}")
    return s3_bucket_prefix_

# ... the rest of the file

我花了一段时间才发现这个问题。如果你自己还没有发现,那么它在这里:

    logging.info(f"The S3 bucket prefix seems to be in the correct format: {s3_bucket_prefix_}")

我意外地使用日志记录来写消息而不是传入的日志记录对象。

我们可以使用日志记录基对象来记录,但是在使用时它会自动创建并添加流处理程序,但没有进行配置。

只需将参数更改为日志记录对象即可彻底解决我的问题。

因此,为了完整起见,代码应如下所示:

    logger_.info(f"The S3 bucket prefix seems to be in the correct format: {s3_bucket_prefix_}")

我希望它能帮助其他人解决这个小麻烦。

解决方案 15:

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

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)

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

解决方案 16:

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

看起来像:

import random

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

解决方案 17:

我在一个记录器中有 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个旋转文件处理程序,而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

请注意,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 包命名空间中的模块名称。

解决方案 18:

这些都不适合我,因为我发现了一种半独特的方法来做到这一点,但它是这里主题的变体。

当创建具有级别、格式等的记录器时,我添加了“两者sys.stdoutsys.stderr并将日志级别设置为相同的值。

删除stderr解决了我的问题。

据我了解,stdout包含所有消息(高于最低日志级别)并且stderr应该是log_level = 'ERROR'默认的。

解决方案 19:

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大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用