【问题标题】:What is the point of setLevel in a python logging handler?python 日志处理程序中 setLevel 的意义何在?
【发布时间】:2013-07-14 04:00:36
【问题描述】:

假设我有以下代码:

import logging
import logging.handlers

a = logging.getLogger('myapp')
h = logging.handlers.RotatingFileHandler('foo.log')
h.setLevel(logging.DEBUG)
a.addHandler(h)

# The effective log level is still logging.WARN
print a.getEffectiveLevel() 
a.debug('foo message')
a.warn('warning message')

我希望在处理程序上设置logging.DEBUG 会导致将调试级别的消息写入日志文件。但是,这会为有效级别打印 30(等于 logging.WARNING,默认值),并且仅将 warn 消息记录到日志文件,而不是调试消息。

似乎处理程序的日志级别正在下降,例如它被默默地忽略了。这让我想知道,为什么处理程序上有setLevel

【问题讨论】:

  • 问得好,但为了连贯性,如果您正在测试 a.getEffectiveLevela.setLevelh.setLevel 更有意义。
  • 在这种情况下,处理程序没有getEffectiveLevel 命令
  • 可以说记录器级别控制哪些日志消息进入并成为日志记录,而处理程序级别控制哪些日志记录出来和发出。我有一个伪代码要点,总结了这里的一些日志记录逻辑:gist.github.com/Stannislav/dd0c44a9b3f6d9e8dd30756582228877

标签: python logging


【解决方案1】:

它允许更精细的控制。默认情况下,root logger 设置了WARNING 级别,这意味着它不会打印级别较低的消息(无论处理程序的级别如何设置!)。但是,如果您将根记录器的级别设置为DEBUG,则确实会将消息发送到日志文件:

import logging
import logging.handlers

a = logging.getLogger('myapp')
a.setLevel(logging.DEBUG)   # set root's level
h = logging.handlers.RotatingFileHandler('foo.log')
h.setLevel(logging.DEBUG)
a.addHandler(h)
print a.getEffectiveLevel() 
a.debug('foo message')
a.warn('warning message')

现在,您要添加一个不记录调试信息的新处理程序的图像。 您可以通过简单地设置处理程序日志记录级别来做到这一点:

import logging
import logging.handlers

a = logging.getLogger('myapp')
a.setLevel(logging.DEBUG)   # set root's level

h = logging.handlers.RotatingFileHandler('foo.log')
h.setLevel(logging.DEBUG)
a.addHandler(h)

h2 = logging.handlers.RotatingFileHandler('foo2.log')
h2.setLevel(logging.WARNING)
a.addHandler(h2)

print a.getEffectiveLevel() 
a.debug('foo message')
a.warn('warning message')

现在,日志文件foo.log 将包含两条消息,而文件foo2.log 将只包含警告消息。您可能对仅包含错误级别消息的日志文件感兴趣,然后只需添加 Handler 并将其级别设置为 logging.ERROR,所有内容都使用相同的 Logger

您可以将Logger 日志记录级别视为对给定记录器及其处理程序哪些消息“感兴趣”的全局限制。记录器随后考虑的消息被发送到处理程序,这些处理程序执行自己的过滤和记录过程。

【讨论】:

  • 因此最佳实践是设置较低级别的根记录器并通过处理程序级别控制记录。我说的对吗?
  • 这不是“最佳实践”,只是做任何其他事情都是无用的。如果处理程序的级别是调试但记录器只发送错误,处理程序当然只会接收(并传递)错误。
  • 有人会奇怪,为什么root logger的默认日志级别不是DEBUG0,其实是因为如果你不知道这个“窍门”,那你就从头开始你的脑袋就像 OP,想知道为什么你的日志消息没有被打印出来。
  • @Christoph 我相信因为DEBUG 是为了调试应用程序,因此应该只在开发人员调试它时出现。将默认级别设置为 `W​​ARNING 可避免在真实用户使用应用程序时某些缺少配置导致打印噪音。换句话说:显示低级消息应该需要用户的干预。 (显然这只是一种观点,人们也可以反对)。
  • 好的,但在这种情况下,据我所知,我至少希望INFO 成为默认值,然后......无论如何,如你所说。
【解决方案2】:

在 Python 日志记录中有两个不同的概念:记录器记录的级别和处理程序实际激活的级别。

当调用 log 时,基本上发生的是:

if self.level <= loglevel:
    for handler in self.handlers:
        handler(loglevel, message)

虽然这些处理程序中的每一个都会调用:

if self.level <= loglevel:
    # do something spiffy with the log!

如果您想对此进行实际演示,可以查看Django's config settings。我将在此处包含相关代码。

LOGGING = {
    #snip
    'handlers': {
        'null': {
            'level': 'DEBUG',
            'class': 'logging.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['special']
        }
    },
    'loggers': {
        #snip
        'myproject.custom': {
            # notice how there are two handlers here!
            'handlers': ['console', 'mail_admins'],
            'level': 'INFO',
            'filters': ['special']
        }
    }
}

因此,在上面的配置中,只有getLogger('myproject.custom').info 及以上的日志才会被处理以进行日志记录。发生这种情况时,控制台将输出所有结果(它将输出所有结果,因为它设置为DEBUG 级别),而mail_admins 记录器将发生在所有ERRORs、FATALs 和@987654330 上@s.

我想一些不是 Django 的代码也可能有帮助:

import logging.handlers as hand
import logging as logging

# to make things easier, we'll name all of the logs by the levels
fatal = logging.getLogger('fatal')
warning = logging.getLogger('warning')
info = logging.getLogger('info')

fatal.setLevel(logging.FATAL)
warning.setLevel(logging.WARNING)
info.setLevel(logging.INFO)    

fileHandler = hand.RotatingFileHandler('rotating.log')

# notice all three are re-using the same handler.
fatal.addHandler(fileHandler)
warning.addHandler(fileHandler)
info.addHandler(fileHandler)

# the handler should log everything except logging.NOTSET
fileHandler.setLevel(logging.DEBUG)

for logger in [fatal,warning,info]:
    for level in ['debug','info','warning','error','fatal']:
        method = getattr(logger,level)
        method("Debug " + logger.name + " = " + level)

# now, the handler will only do anything for *fatal* messages...
fileHandler.setLevel(logging.FATAL)

for logger in [fatal,warning,info]:
    for level in ['debug','info','warning','error','fatal']:
        method = getattr(logger,level)
        method("Fatal " + logger.name + " = " + level)

结果:

Debug fatal = fatal
Debug warning = warning
Debug warning = error
Debug warning = fatal
Debug info = info
Debug info = warning
Debug info = error
Debug info = fatal
Fatal fatal = fatal
Fatal warning = fatal
Fatal info = fatal

再次注意,当日志处理程序设置为DEBUG,但当处理程序设置为FATAL 时,info 如何在infowarningerrorfatal 记录一些内容突然间只有FATAL 消息进入文件。

【讨论】:

    【解决方案3】:

    处理程序代表记录事件的不同受众。处理程序上的级别用于控制特定受众看到的输出的详细程度,并在记录器上设置的任何级别之外执行。记录器的级别用于控制来自应用程序或库的不同部分的日志记录的整体详细程度。

    有关如何处理日志事件的更多信息,请参阅this diagram

    【讨论】:

    • 你好@Vinay Sajip!所以如果你省略 Handler 的级别,它默认是什么?我正在使用dictConfig 方法为我的应用程序全局执行一些自定义日志配置,并且我已经阅读了文档,它说处理程序的级别是 optional 但它没有说明它是什么默认为。我也无法从上图中推断出这一点,因为当它到达Handler enabled for level of LogRecord? 部分时,我如何判断处理程序是否已启用?这是否意味着如果它没有明确的级别设置,它会继承它所附加的记录器的级别?还是根记录器?
    • 默认允许所有事件通过。
    • @MariusMucenicu 您还可以通过.level 属性检查处理程序或记录器的级别,例如file_handler.level
    【解决方案4】:

    规则

    当且仅当

    handler.level <= message.level
    &&
    logger.level <= message.level
    

    然后打印消息。

    提醒:值越小越详细

    Level    | Numeric value
    ---------|--------------
    CRITICAL | 50
    ERROR    | 40
    WARNING  | 30
    INFO     | 20
    DEBUG    | 10
    NOTSET   | 0
    

    参考:https://docs.python.org/3/library/logging.html#logging-levels

    换句话说

    如果记录器设置为WARNING,则处理程序是否具有更详细的设置无关紧要。它在到达处理程序时已经被过滤掉了。

    一个完整的例子

    import logging
    
    
    handler_info = logging.StreamHandler()
    handler_info.setLevel("INFO")
    handler_info.setFormatter(logging.Formatter(
        f"%(levelname)s message for %(name)s handled by handler_info: %(message)s"))
    
    handler_debug = logging.StreamHandler()
    handler_debug.setLevel("DEBUG")
    handler_debug.setFormatter(logging.Formatter(
        f"%(levelname)s message for %(name)s handled by handler_debug: %(message)s"))
    
    logger_info = logging.getLogger('logger_info')
    logger_info.setLevel("INFO")
    logger_info.addHandler(handler_info)
    logger_info.addHandler(handler_debug)
    
    logger_debug = logging.getLogger('logger_debug')
    logger_debug.setLevel("DEBUG")
    logger_debug.addHandler(handler_info)
    logger_debug.addHandler(handler_debug)
    
    print()
    print("output for `logger_info.info('hello')`")
    logger_info.info("hello")
    print()
    print("output for `logger_info.debug('bonjour')`")
    logger_info.debug("bonjour")
    print()
    print("output for `logger_debug.info('hola')`")
    logger_debug.info("hola")
    print()
    print("output for `logger_debug.debug('ciao')`")
    logger_debug.debug("ciao")
    print()
    

    给了

    output for `logger_info.info('hello')`
    INFO message for logger_info handled by handler_info: hello
    INFO message for logger_info handled by handler_debug: hello
    
    output for `logger_info.debug('bonjour')`
    # nothing, because message.level < logger.level
    
    output for `logger_debug.info('hola')`
    INFO message for logger_debug handled by handler_info: hola
    INFO message for logger_debug handled by handler_debug: hola
    
    output for `logger_debug.debug('ciao')`
    DEBUG message for logger_debug handled by handler_debug: ciao
    # nothing from handler_info, because message.level < handler.level
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-03
      • 1970-01-01
      • 1970-01-01
      • 2011-03-08
      • 2017-03-06
      • 2018-10-29
      • 1970-01-01
      • 2022-06-21
      相关资源
      最近更新 更多