我正在使用Python日志记录,出于某种原因,我的所有消息都出现了两次。
我有一个配置日志记录的模块:
# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
self.logger = logging.getLogger("my_logger")
self.logger.setLevel(logging.DEBUG)
self.logger.propagate = 0
# Format for our loglines
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# Setup console logging
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
self.logger.addHandler(ch)
# Setup file logging as well
fh = logging.FileHandler(LOG_FILENAME)
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
self.logger.addHandler(fh)
稍后,我调用此方法来配置日志记录:
if __name__ == '__main__':
tom = Boy()
tom.configure_logging(LOG_FILENAME)
tom.buy_ham()
然后说,buy_ham模块,我打电话给:
self.logger.info('Successfully able to write to %s' % path)
由于某种原因,所有消息都出现了两次。我注释掉了一个流处理程序,仍然是一样的。有点怪异,不知道为什么会发生这种情况......哈哈。假设我错过了一些明显的东西。
干杯, 维克多
答案 0 :(得分:93)
您正在拨打configure_logging
两次(可能使用__init__
Boy
方法):getLogger
将返回相同的对象,但addHandler
不会检查如果已经将类似的处理程序添加到记录器中。
尝试跟踪对该方法的调用并删除其中一个。或者在logging_initialized
False
方法中将__init__
初始化为Boy
,并在configure_logging
为{logging_initialized
时更改True
不做任何操作1}},并在初始化记录器后将其设置为True
。
如果您的程序创建了多个Boy
实例,则必须使用添加处理程序的全局configure_logging
函数更改执行操作的方式,并且Boy.configure_logging
方法仅初始化self.logger
属性。
解决此问题的另一种方法是检查记录器的处理程序属性:
logger = logging.getLogger('my_logger')
if not logger.handlers:
# create the handlers and call logger.addHandler(logging_handler)
答案 1 :(得分:7)
如果您看到此问题并且您没有添加处理程序两次,请参阅abarnert的回答here
来自docs:
注意:如果您将处理程序附加到记录器及其中的一个或多个 祖先,它可能会多次发出相同的记录。一般来说,你 不应该将处理程序附加到多个记录器 - 如果你 只需将它附加到最适合的记录器中 记录器层次结构,然后它将看到所有后代记录的所有事件 记录器,只要它们的传播设置保持设置为True。一个 常见的情况是仅将处理程序附加到根记录程序,并将其附加到 让传播照顾其余部分。
因此,如果你想在“test”上使用自定义处理程序,并且你不希望它的消息也转到根处理程序,答案很简单:关闭它的传播标志:
logger.propagate = False
答案 2 :(得分:6)
每次从外面打电话时都会添加处理程序。完成工作后尝试删除处理程序:
self.logger.removeHandler(ch)
答案 3 :(得分:5)
我是一个蟒蛇新手,但这似乎对我有用(Python 2.7)
while logger.handlers:
logger.handlers.pop()
答案 4 :(得分:1)
如果您(意外地)将某些内容输出到记录器然后进行配置,似乎为时已晚。例如,在我的代码中,我有
logging.warning("look out)"
...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)
root.info("hello")
我会得到类似的信息(忽略格式选项)
look out
hello
hello
,所有内容都两次写入标准输出。我相信这是因为第一次调用logging.warning
会自动创建一个新的处理程序,然后我显式添加了另一个处理程序。当我删除意外的第一个logging.warning
呼叫时,问题消失了。
答案 5 :(得分:0)
如果未安装root处理程序,则对logging.debug()
的调用将调用logging.basicConfig()
。对于我来说,这是在无法控制测试用例触发顺序的测试框架中发生的。我的初始化代码正在安装第二个。默认使用我不需要的logging.BASIC_FORMAT。
答案 6 :(得分:0)
就我而言,我将设置logger.propagate = False
以防止重复打印。
在下面的代码中,如果您删除logger.propagate = False
,则会看到两次打印。
import logging
from typing import Optional
_logger: Optional[logging.Logger] = None
def get_logger() -> logging.Logger:
global _logger
if _logger is None:
raise RuntimeError('get_logger call made before logger was setup!')
return _logger
def set_logger(name:str, level=logging.DEBUG) -> None:
global _logger
if _logger is not None:
raise RuntimeError('_logger is already setup!')
_logger = logging.getLogger(name)
_logger.handlers.clear()
_logger.setLevel(level)
ch = logging.StreamHandler()
ch.setLevel(level)
# warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
ch.setFormatter(_get_formatter())
_logger.addHandler(ch)
_logger.propagate = False # otherwise root logger prints things again
def _get_formatter() -> logging.Formatter:
return logging.Formatter(
'[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
答案 7 :(得分:0)
我遇到了奇怪的情况,控制台日志增加了一倍,但文件日志却没有。经过一番挖掘,我弄清楚了。
请注意,第三方软件包可以注册记录器。这是要提防的事情(在某些情况下是无法避免的)。在许多情况下,第三方代码会检查是否存在任何现有的 root 记录程序处理程序;如果没有,他们会注册一个新的控制台处理程序。
我的解决方案是在根目录下注册控制台记录器:
rootLogger = logging.getLogger() # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
rootLogger.addHandler(ch)
答案 8 :(得分:0)
在多个过程中,我一直在同一个问题上挣扎。 (有关代码,请参见docs,我几乎是逐字遵循的。)即,源自任何子进程的所有日志消息都重复了。
我的错误是打电话给worker_configurer()
,
def worker_configurer(logging_queue):
queue_handler = logging.handlers.QueueHandler(logging_queue)
root = logging.getLogger()
root.addHandler(queue_handler)
root.setLevel(level)
既在子进程中也在主进程中(因为我也希望主进程也记录日志)。导致麻烦的原因(在我的Linux机器上)是在Linux上,子进程是通过派生开始的,因此从主进程继承了现有的日志处理程序。也就是说,在Linux上QueueHandler
被注册了两次。
现在,阻止QueueHandler
在worker_configurer()
函数中两次注册并不像看起来那么简单:
诸如根记录器root
之类的记录器对象具有handlers
属性,但未记录。
以我的经验,即使any([handler is queue_handler for handler in root.handlers])
似乎包含相同的any([handler == queue_handler for handler in root.handlers])
,在分叉后测试root.handlers
(身份)还是QueueHandler
(相等)是否失败。 (显然,由于queue_handler in root.handlers
运算符checks for both identity and equality in the case of lists的存在,前两个表达式可以用in
缩写。)
root logger通过pytest之类的软件包进行了修改,因此root.handlers
和root.hasHandlers()
一开始并不是很可靠。 (毕竟它们是全局状态。)
因此,干净的解决方案是用生成代替分支,以从一开始就防止此类多处理错误(当然,前提是您可以忍受更多的内存占用)。或者使用logging
包的替代方案,该替代方案不依赖于全局状态,而是要求您进行适当的依赖注入,但是我在偏离方向……:)
话虽如此,我最终还是做了一次琐碎的检查:
def worker_configurer(logging_queue):
queue_handler = logging.handlers.QueueHandler(logging_queue)
root = logging.getLogger()
for handler in root.handlers:
if isinstance(handler, logging.handlers.QueueHandler):
return
root.addHandler(queue_handler)
root.setLevel(level)
很明显,当我决定在其他地方注册第二个队列处理程序时,这将带来讨厌的副作用。