问题:日志消息使用Python Logging出现两次
我正在使用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
您正在调用configure_logging
两次(也许使用的__init__
方法Boy
):getLogger
将返回相同的对象,但addHandler
不检查是否已将类似的处理程序添加到记录器。
尝试跟踪对该方法的调用并消除其中之一。或设置一个标志logging_initialized
False
在__init__
方法中初始化为,如果is为,Boy
则更configure_logging
改为不执行任何logging_initialized
操作True
,并True
在初始化记录器后将其设置为。
如果程序创建了多个Boy
实例,则必须使用configure_logging
添加处理程序的全局函数来更改处理方式,并且该Boy.configure_logging
方法只能初始化self.logger
属性。
解决此问题的另一种方法是检查记录器的handlers属性:
logger = logging.getLogger('my_logger')
if not logger.handlers:
# create the handlers and call logger.addHandler(logging_handler)
回答 1
如果您看到此问题并且没有两次添加处理程序,则请参阅abarnert的答案 在此处
来自文档:
注意:如果将处理程序附加到记录器及其一个或多个祖先,则它可能会多次发出相同的记录。通常,您不需要将一个处理程序附加到一个以上的记录器上-如果您将它附加到记录器层次结构中最高的适当记录器上,则它将看到所有后代记录器记录的所有事件,前提是它们的传播设置保留为True。一种常见的情况是仅将处理程序附加到根记录器,并让传播来处理其余部分。
因此,如果您希望在“测试”上使用自定义处理程序,并且不希望其消息也发送到根处理程序,则答案很简单:关闭其传播标志:
logger.propagate = False
回答 2
每次您从外部调用时都会添加该处理程序。完成工作后,尝试删除处理程序:
self.logger.removeHandler(ch)
回答 3
我是python新手,但这似乎对我有用(Python 2.7)
while logger.handlers:
logger.handlers.pop()
回答 4
就我而言,我要设定 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')
回答 5
如果未安装根处理程序,则logging.debug()
调用呼叫logging.basicConfig()
。对于我来说,这是在无法控制测试用例触发顺序的测试框架中发生的。我的初始化代码正在安装第二个。默认使用我不需要的logging.BASIC_FORMAT。
回答 6
看来,如果您(意外地)将某些内容输出到记录器然后进行配置,那就太迟了。例如,在我的代码中
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
电话时,问题消失了。
回答 7
我遇到了奇怪的情况,控制台日志增加了一倍,但文件日志却没有。经过一番挖掘,我弄清楚了。
请注意,第三方软件包可以注册记录器。这是要提防的事情(在某些情况下是无法避免的)。在许多情况下,第三方代码会检查是否存在任何根目录 logger处理程序;例如,如果没有,他们会注册一个新的控制台处理程序。
我的解决方案是在根目录下注册控制台记录器:
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)