【问题标题】:Removing handlers from python's logging loggers从 python 的日志记录器中删除处理程序
【发布时间】:2011-11-21 00:25:23
【问题描述】:

我正在使用 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))    

我希望在循环结束时,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

更有趣的是,如果我用下面的循环替换原来的循环,循环 按预期工作,循环结束时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))

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

【问题讨论】:

  • for h in list(testLogger.handlers)

标签: python logging


【解决方案1】:

这不是特定于记录器的行为。 从不 改变(插入/删除元素)您当前正在迭代的列表。如果需要,请制作副本。在这种情况下,testLogger.handlers = [] 应该可以解决问题。

【讨论】:

  • 解决方案不是那么明显和好,因为我们需要考虑线程安全,这显然在这里受到了损害(看看 addHandler 实现 - 在那里使用锁来修改处理程序列表)。
  • 更安全的选择是修改现有列表:testLogger.handlers.clear()
  • @zenofpython 我想testLogger.handlers.clear() 调用list 类型的clear 方法?为什么比testLogger.handlers = []更安全?
  • 每当您在某处使用指向原始列表的别名时,重新定义列表而不是修改原始列表都是一个问题。假设:a = b = [1, 2, 3]a = [] 注意b 仍然指向[1, 2, 3]。公平地说,在这种情况下,您不太可能使用这样的别名,但保持一致性是一种很好的做法,这样您就不会因偷工减料而感到意外。
【解决方案2】:

如果你不想全部删除(感谢the tip@CatPlusPlus):

testLogger.handlers = [
    h for h in testLogger.handlers if not isinstance(h, logging.StreamHandler)]

【讨论】:

  • 对不起,我发现这不起作用。因为 isinstance 不能用来判断处理程序的类型
  • @fat_cheng 为什么 isinstance 不能用于此目的?我自己使用这个答案中的建议似乎没有问题。
  • 我在 Ubuntu 16.04 中使用 python3。我改用if (type(h) == logging.StreamHandler)]
  • @fat_cheng 你不应该这样做,因为type 不检查父类型(继承),isinstance 会:stackoverflow.com/a/1549854/623735
  • 出于个人目的,我想检查一个handker 是否在之后没有向slack 频道发送垃圾邮件。 handlers = logger.handlers; logger.error('Removing slack handler'); logger.handlers = [handler for handler in logger.handlers if handler != slack_handler]
【解决方案3】:

而不是改变未记录的.handler

选项 1

logging.getLogger().removeHandler(logging.getLogger().handlers[0])

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

logger = logging.getLogger()
while logger.hasHandlers():
    logger.removeHandler(logger.handlers[0])

选项 2

logging.config.dictConfig(config={'level': logging.DEBUG, 'handlers': []}

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

【讨论】:

  • 选项 1 不适用于非根记录器:方法 hasHandlers() 还检查祖先记录器的处理程序,因此如果祖先记录器有处理程序,则 logger.hasHandlers() 将始终评估为 @ 987654328@,因此logger.handlers[0] 最终会引发IndexError
  • 关闭选项 2,您也可以将实例化的 logger 对象的 handlers 属性设置为空数组,例如 logger = logging.getLogger(__name__)logger.handlers = []
【解决方案4】:

我刚刚发现您也可以在日志记录 .ini 文件中执行此操作,使用以下代码块:

[logger_stpipe]
handlers=
propagate=1
qualname=stpipe

它基本上会停用给定记录器的所有处理程序。但这有点受限,因为您必须提前知道 Logger 的名称。

【讨论】:

    【解决方案5】:

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

    logger = logging.getLogger(__name__)
    logger.propagate = False
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-05
      • 1970-01-01
      • 2020-03-27
      相关资源
      最近更新 更多