从python的日志记录器中删除处理程序

Bor*_*lik 47 python logging

我正在玩Python的日志记录系统.我在循环中从Logger对象中删除处理程序时发现了一个奇怪的行为.也就是说,我的for循环删除了除一个处理程序以外 额外调用以.removeHandler顺利删除最后一个处理程序.呼叫期间不会发出任何错误消息.

这是测试代码:

import logging
import sys
logging.basicConfig()
dbg = logging.getLogger('dbg')
dbg.setLevel(logging.DEBUG)

testLogger = logging.getLogger('mylogger')
sh = logging.StreamHandler(sys.stdout)
fh = logging.FileHandler('mylogfile.log')
dbg.debug('before adding handlers: %d handlers'%len(testLogger.handlers))
testLogger.addHandler(fh)
testLogger.addHandler(sh)

dbg.debug('before removing. %d handlers: %s'%(len(testLogger.handlers), 
                                              str(testLogger.handlers)))
for h in testLogger.handlers:
    dbg.debug('removing handler %s'%str(h))
    testLogger.removeHandler(h)
    dbg.debug('%d more to go'%len(testLogger.handlers))

#HERE I EXPECT THAT NO HANDLER WILL REMAIN    
dbg.debug('after removing: %d handlers: %s'%(len(testLogger.handlers), 
                                              str(testLogger.handlers)))
if len(testLogger.handlers) > 0:
    #Why is this happening?
    testLogger.removeHandler(testLogger.handlers[0])
dbg.debug('after manually removing the last handler: %d handlers'%len(testLogger.handlers))    
Run Code Online (Sandbox Code Playgroud)

我希望在循环结束时没有处理程序将保留在testLogger对象中,但是最后一次调用.removeHandler显然会失败,从下面的输出中可以看出.然而,对此函数的额外调用会按预期删除处理程序.这是输出:

DEBUG:dbg:before adding handlers: 0 handlers
DEBUG:dbg:before removing. 2 handlers: [<logging.FileHandler instance at 0x021263F0>, <logging.StreamHandler instance at 0x021262B0>]
DEBUG:dbg:removing handler <logging.FileHandler instance at 0x021263F0>
DEBUG:dbg:1 more to go
DEBUG:dbg:after removing: 1 handlers: [<logging.StreamHandler instance at 0x021262B0>]
DEBUG:dbg:after manually removing the last handler: 0 handlers
Run Code Online (Sandbox Code Playgroud)

更有趣的是,如果我用下面的循环替换原始循环,循环按预期工作,并且循环testLogger结束时没有处理程序保留在对象中.这是修改后的循环:

while len(testLogger.handlers) > 0:
    h = testLogger.handlers[0]
    dbg.debug('removing handler %s'%str(h))
    testLogger.removeHandler(h)
    dbg.debug('%d more to go'%len(testLogger.handlers))
Run Code Online (Sandbox Code Playgroud)

什么解释了这种行为?这是一个错误还是我错过了什么?

Cat*_*lus 99

这不是特定于记录器的行为.永远不要改变(插入/删除元素)您当前正在迭代的列表.如果需要,请复制.在这种情况下testLogger.handlers = []应该做的伎俩.

  • 每当您在某个地方使用指向原始列表的别名时,重新定义列表而不是修改原始列表就会出现问题。假设: `a = b = [1, 2, 3]` `a = []` 请注意,`b` 仍然指向 `[1, 2, 3]`。公平地说,*在这种情况下*您不太可能使用这样的别名,但保持一致性是一个很好的做法,这样您就不会因为偷工减料而得到任何意外的惊喜。 (8认同)
  • 呵呵呵:我错过了将列表设置为[]的明显解决方案.我想我需要更多咖啡 (7认同)
  • 解决方案不是那么明显和好,因为我们需要考虑线程安全,这显然在这里受到损害(看看 addHandler 实现 - 在那里使用锁来修改处理程序列表)。 (3认同)
  • 更安全的选择是修改现有列表:`testLogger.handlers.clear()` (3认同)

Roh*_*hit 13

我想说,如果删除日志记录处理程序的目的是为了防止将日志发送到其他处理程序(大多数情况下都是这种情况),那么我遵循的另一种方法是为当前记录器设置propagate为。False

logger = logging.getLogger(__name__)
logger.propagate = False
Run Code Online (Sandbox Code Playgroud)

这样,您的日志消息只会发送到您显式添加的处理程序,而不会传播到父记录器。


hob*_*obs 12

如果您不想全部删除它们(感谢提示 @CatPlusPlus):

testLogger.handlers = [
    h for h in testLogger.handlers if not isinstance(h, logging.StreamHandler)]
Run Code Online (Sandbox Code Playgroud)

  • @fat_cheng为什么不能为此目的工作?我自己在这个答案中使用这个建议似乎没有问题. (2认同)

qrt*_*tLs 9

而不是变异 undocumented .handler

选项1

logging.getLogger().removeHandler(logging.getLogger().handlers[0])
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以通过官方 api 完全删除预先存在的处理程序对象。或者删除所有处理程序:

logger = logging.getLogger()
while logger.hasHandlers():
    logger.removeHandler(logger.handlers[0])
Run Code Online (Sandbox Code Playgroud)

选项 2

logging.config.dictConfig(config={'level': logging.DEBUG, 'handlers': []}
Run Code Online (Sandbox Code Playgroud)

不仅删除而且阻止它的创建。列表根将有[]处理程序

  • 选项 1 不适用于非根记录器:方法“hasHandlers()”还会检查祖先记录器的处理程序,因此如果祖先记录器有处理程序,则“logger.hasHandlers()”将始终评估为“True”因此 `logger.handlers[0]` 最终将引发 `IndexError`。 (2认同)