使用 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)
由于某种原因,所有消息都出现两次。我注释掉了其中一个流处理程序,仍然是同样的事情。有点奇怪,不知道为什么会发生这种情况。我假设我错过了一些明显的事情。
I'm using Python logging, and for some reason, all of my messages are appearing twice.
I have a module to configure logging:
# 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)
Later on, I call this method to configure logging:
if __name__ == '__main__':
tom = Boy()
tom.configure_logging(LOG_FILENAME)
tom.buy_ham()
And then within say, the buy_ham module, I'd call:
self.logger.info('Successfully able to write to %s' % path)
And for some reason, all the messages are appearing twice. I commented out one of the stream handlers, still the same thing. Bit of a weird one, not sure why this is happening. I'm assuming I've missed something obvious.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
我遇到了一种奇怪的情况,控制台日志加倍,但我的文件日志却没有。经过大量挖掘后,我明白了。
请注意,第三方软件包可以注册记录器。这是需要注意的事情(在某些情况下是无法避免的)。在许多情况下,第三方代码会检查是否存在任何现有的root记录器处理程序;如果没有,他们会注册一个新的控制台处理程序。
我的解决方案是在根级别注册我的控制台记录器:
I was getting a strange situation where console logs were doubled but my file logs were not. After a ton of digging I figured it out.
Please be aware that third party packages can register loggers. This is something to watch out for (and in some cases can't be prevented). In many cases third party code checks to see if there are any existing root logger handlers; and if there isn't--they register a new console handler.
My solution to this was to register my console logger at the root level:
如果您使用任何配置进行日志记录,例如
log.conf
在 .conf 文件中,您可以通过在
[logger_myLogger]
部分中添加以下行来实现:传播=0
If you are using any config for logging, For instance
log.conf
In .conf file you can do it by adding this line in the
[logger_myLogger]
section:propagate=0
如果您使用标准构造
logger =logging.getLogger('mymodule')
,然后不小心将logger
输入为logging
,即这将导致重复由于调用
logging
创建了一个新的记录器,因此会出现消息。If you use the standard construction
logger = logging.getLogger('mymodule')
and then accidentally mistypeloggger
aslogging
i.e.then this will cause duplicate messages to come up because the call to
logging
creates a new logger.我有同样的问题。就我而言,这不是由于处理程序或重复的初始配置,而是由于一个愚蠢的拼写错误。在
main.py
中,我使用了logger
对象,但在my_tool.py
中,我直接调用了logging
模块错误,因此在从my_tool
模块调用函数后,一切都变得混乱并且消息看起来重复。这是代码:
main.py
my_tool.py
和结果
terminal
I had the same issue. In my case, it was not due to handlers or duplicate initial configuration but a stupid typo. In
main.py
I was using alogger
object but inmy_tool.py
I was directly calling to thelogging
module by mistake, hence after invoking functions frommy_tool
module everything was messed up and the messages appeared duplicated.This was the code:
main.py
my_tool.py
and the result
terminal
就我而言,我看到 INFO 日志消息两次,因为我在 Bottle 实例的 run() 调用中使用了 reloader=True 。
语境:
In my case I saw the INFO log message twice because I used
reloader=True
in the run() call of the bottle instance.Context:
值得注意的是,如果您使用 Django 及其 runserver 命令,并且还运行自动重新加载功能(如果 DEBUG 设置为 true,则会自动发生),您将看到两次输出,因为自动重新加载进程还会初始化第二次应用程序。要解决此问题,您可以执行
runserver --noreload
,或者:为什么在 Django 开发服务器中 run 被调用两次?Worth noting, if you're using Django and its runserver command, if you are also running the auto reload feature (which will happen automatically if DEBUG is set to true), you will see the output twice, because the autoreloader process also initializes the app a second time. To fix this you can do
runserver --noreload
, or: Why is run called twice in the Django dev server?我遇到了同样的问题,有一个简单的方法可以解决这个问题。在自定义日志文件中,添加此行
现在,当应用程序初始化时,您将看到两行显示重复的导入。问题是我以两种不同的方式导入模块
并将
所有内容更改为从 base_pkg 导入,问题就解决了。
I had the same issue, and there is an easy way to fix this. In the custom log file, add this line
Now, when the application initializes, you will see two lines showing the imports where it is getting duplicated. The issue was that I was importing the module in two different ways
and
Change everything to import from base_pkg and the issue was solved.
您正在调用
configure_logging
两次(可能在Boy
的__init__
方法中):getLogger
将返回相同的对象,但 addHandler 不会检查是否已将类似的处理程序添加到记录器中。尝试跟踪对该方法的调用并消除其中之一。或者在
Boy
的__init__
方法中设置一个标志logging_initialized
初始化为False
,并更改configure_logging< /code> 如果
logging_initialized
为True
则不执行任何操作,并在初始化记录器后将其设置为True
。如果您的程序创建了多个
Boy
实例,则必须使用添加处理程序的全局configure_logging
函数以及Boy.configure_logging
方法仅初始化self.logger
属性。解决此问题的另一种方法是检查记录器的 handlers 属性:
You are calling
configure_logging
twice (maybe in the__init__
method ofBoy
) :getLogger
will return the same object, butaddHandler
does not check if a similar handler has already been added to the logger.Try tracing calls to that method and eliminating one of these. Or set up a flag
logging_initialized
initialized toFalse
in the__init__
method ofBoy
and changeconfigure_logging
to do nothing iflogging_initialized
isTrue
, and to set it toTrue
after you've initialized the logger.If your program creates several
Boy
instances, you'll have to change the way you do things with a globalconfigure_logging
function adding the handlers, and theBoy.configure_logging
method only initializing theself.logger
attribute.Another way of solving this is by checking the handlers attribute of your logger:
如果您遇到此问题并且没有两次添加处理程序,请参阅 abarnert 的答案 此处
来自 文档:
因此,如果您想要在“test”上使用自定义处理程序,并且不希望其消息也发送到根处理程序,答案很简单:关闭其
propagate
标志:If you are seeing this problem and you're not adding the handler twice then see abarnert's answer here
From the docs:
So, if you want a custom handler on "test", and you don't want its messages also going to the root handler, the answer is simple: turn off its
propagate
flag:我是 python 新手,但这似乎对我有用(Python 2.7)
I'm a python newbie, but this seemed to work for me (Python 2.7)
就我而言,我会设置 logger.propagate = False 来防止重复打印。
在下面的代码中,如果删除 logger.propagate = False ,那么您将看到重复打印。
In my case I'd to set
logger.propagate = False
to prevent double printing.In below code if you remove
logger.propagate = False
then you will see double printing.每次从外部调用时都会添加处理程序。完成工作后尝试删除处理程序:
The handler is added each time you call from outside. Try Removeing the Handler after you finish your job:
如果您尝试从父文件创建日志记录对象,也可能会发生这种情况。例如
这是主应用程序文件
test.py
下面是父文件
main.py
这个输出只会打印一次
但是如果我们有一个父日志记录对象,那么它将被打印两次。例如,如果这是父文件,
则输出将是
This can also happen if you are trying to create a logging object from the parent file. For e.g.
This is the main application file
test.py
And below is the parent file
main.py
The output of this will print only once
But if we had a parent logging object, then it will be printed twice. For e.g. if this is the parent file
The the output will be
添加到其他人的有用响应...
万一您没有意外地在记录器上配置多个处理程序:
当您的记录器有祖先(根记录器始终是一个)并且它们有自己的处理程序时,当您的记录器输出(默认情况下)时,它们也会输出,这将创建重复项。您有两个选项:
Adding to others' useful responses...
In case you are NOT accidentally configuring more than one handler on your logger:
When your logger has ancestors(root logger is always one) and they have their own handlers, they will also output when your logger outputs (by default), which will create duplicates. You have two options:
如果没有安装根处理程序,则对
logging.debug()
的调用将调用logging.basicConfig()
。这发生在我的测试框架中,我无法控制测试用例的触发顺序。我的初始化代码正在安装第二个。默认使用我不想要的logging.BASIC_FORMAT。A call to
logging.debug()
callslogging.basicConfig()
if there are no root handlers installed. That was happening for me in a test framework where I couldn't control the order that test cases fired. My initialization code was installing the second one. The default uses logging.BASIC_FORMAT that I didn't want.看来,如果你(不小心)向记录器输出一些内容然后配置它,那就太晚了。例如,在我的代码中,
我会得到类似的东西(忽略格式选项),
并且所有内容都被写入标准输出两次。我相信这是因为第一次调用
logging.warning
自动创建一个新的处理程序,然后我显式添加了另一个处理程序。当我删除意外的第一个logging.warning
调用时,问题就消失了。It seems that if you output something to the logger (accidentally) then configure it, it is too late. For example, in my code I had
I would get something like (ignoring the format options)
and everything was written to stdout twice. I believe this is because the first call to
logging.warning
creates a new handler automatically, and then I explicitly added another handler. The problem went away when I removed the accidental firstlogging.warning
call.我在多个进程的背景下遇到了同样的问题。 (有关代码,请参阅 docs 我几乎逐字关注。)也就是说,源自任何子进程的所有日志消息都被复制。
我的错误是在子进程和主进程中都调用了worker_configurer()
(因为我也希望主进程记录内容)。这导致麻烦(在我的 Linux 机器上)的原因是,在 Linux 上,子进程是通过分叉启动的,因此从主进程继承了现有的日志处理程序。也就是说,在 Linux 上,
QueueHandler
注册了两次。现在,防止
QueueHandler
在worker_configurer()
函数中注册两次并不像看起来那么简单:像根记录器这样的 Logger 对象
root
有一个handlers
属性,但没有记录。根据我的经验,测试是否
any([handler is queue_handler for handler in root.handlers])
(identity) 或any([handler == queue_handler for handler in root.handlers] ])
(相等)在分叉后失败,即使root.handlers
看似包含相同的QueueHandler
。 (显然,前两个表达式可以缩写为root.handlers中的queue_handler,因为in
运算符检查列表情况下的同一性和相等性。)根记录器被修改为像 pytest 这样的包,所以
root.handlers
和root.hasHandlers()
一开始就不是很可靠。 (毕竟,它们是全局状态。)因此,干净的解决方案是用生成代替分叉,以从一开始就防止此类多处理错误(当然,前提是您可以忍受额外的内存占用)。或者使用
logging
包的替代方案,它不依赖于全局状态,而是要求您进行适当的依赖项注入,但我离题了……:)话虽这么说,我最终还是去了对于一个相当简单的检查:
显然,当我决定在其他地方注册第二个队列处理程序时,这会产生令人讨厌的副作用。
I was struggling with the same issue in the context of multiple processes. (For the code see the docs which I was following almost verbatim.) Namely, all log messages originating from any of the child processes got duplicated.
My mistake was to call
worker_configurer()
,both in the child processes and also in the main process (since I wanted the main process to log stuff, too). The reason this led to trouble (on my Linux machine) is that on Linux the child processes got started through forking and therefore inherited the existing log handlers from the main process. That is, on Linux the
QueueHandler
got registered twice.Now, preventing the
QueueHandler
from getting registered twice in theworker_configurer()
function is not as trivial as it seems:Logger objects like the root logger
root
have ahandlers
property but it is undocumented.In my experience, testing whether
any([handler is queue_handler for handler in root.handlers])
(identity) orany([handler == queue_handler for handler in root.handlers])
(equality) fails after forking, even ifroot.handlers
seemingly contains the sameQueueHandler
. (Obviously, the previous two expressions can be abbreviated byqueue_handler in root.handlers
, since thein
operator checks for both identity and equality in the case of lists.)The root logger gets modified by packages like pytest, so
root.handlers
androot.hasHandlers()
are not very reliable to begin with. (They are global state, after all.)The clean solution, therefore, is to replace forking with spawning to prevent these kinds of multiprocessing bugs right from the start (provided you can live with the additional memory footprint, of course). Or to use an alternative to the
logging
package that doesn't rely on global state and instead requires you to do proper dependency injection but I'm digressing… :)With that being said, I ended up going for a rather trivial check:
Obviously, this will have nasty side effects the second I decide to register a second queue handler somewhere else.
来自文档:
确保不要使用相同的名称初始化记录器
我建议您使用
__name__
作为名称参数初始化记录器,即:注意:
即使您从具有相同名称的其他模块初始化记录器,您仍然会获得相同的记录器,因此调用 ie
logger.info('somthing')
将记录与启动记录器类一样多的次数。From the docs:
Make sure you don't initialise your loggers with the same name
I advise you to initialise the logger with
__name__
as name param i.e:NOTE:
even if you init a loggers from other modules with same name, you will still get the same logger, therefore calling i.e
logger.info('somthing')
will log as many times as you initiated the logger class.我尝试了很多东西,最后想出了我的自定义记录器。您只需将其
复制
并粘贴
到您的代码中,它就会完成您所需的工作。为什么要使用?
您将得到什么 [ *在您的 colsole/日志文件中它将具有不同的颜色 ]
logger.info("这是信息日志")
logger.warning("这是警告日志")
logger.error("这是错误日志")
logger.ritic(“这是关键日志”)
logger.debug(“这是调试日志”)
注意-我在上面的代码中禁用了调试级别日志,以使用注释/删除行。
I tried many things and finally came up with my custom logger. You just need to
copy
andpaste
it in your code and it will do the work you required.Why to use?
What you will get [ *it will be with different colours in you colsole/log file ]
logger.info("This is info log")
logger.warning("This is warning log")
logger.error("This is error log")
logger.critical("This is critical log")
logger.debug("This is debug log")
Note - I have disabled the debug level log in the above code, to use comment/remove the lines.