使用 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('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.stdout
”sys.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
扫码咨询,免费领取项目管理大礼包!