如何将自定义日志级别添加到Python的日志记录工具中

tue*_*ist 102 python logging python-logging

我想为我的应用程序提供loglevel TRACE(5),因为我认为这debug()还不够.另外log(5, msg)不是我想要的.如何将自定义日志级别添加到Python记录器?

我有mylogger.py以下内容:

import logging

@property
def log(obj):
    myLogger = logging.getLogger(obj.__class__.__name__)
    return myLogger
Run Code Online (Sandbox Code Playgroud)

在我的代码中,我以下列方式使用它:

class ExampleClass(object):
    from mylogger import log

    def __init__(self):
        '''The constructor with the logger'''
        self.log.debug("Init runs")
Run Code Online (Sandbox Code Playgroud)

现在我想打个电话 self.log.trace("foo bar")

在此先感谢您的帮助.

编辑(2016年12月8日):我改变了接受pfa的答案,即恕我直言,这是一个很好的解决方案,基于Eric S.的非常好的建议.

pfa*_*pfa 150

@Eric S.

Eric S.的答案非常好,但我通过实验了解到,这将始终导致在新调试级别记录的消息被打印 - 无论日志级别设置为什么.因此,如果您将新的级别数设置为9,如果调用setLevel(50),将错误地打印较低级别的消息.为防止这种情况发生,您需要在"debugv"函数中使用另一行来检查是否实际启用了相关的日志记录级别.

修复了检查日志记录级别是否已启用的示例:

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    if self.isEnabledFor(DEBUG_LEVELV_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv
Run Code Online (Sandbox Code Playgroud)

如果你看一下代码9setLevel(50)的Python 2.7,这是所有标准的日志功能做(.critical,的.debug等).

我显然无法回复别人的答案因为缺乏声誉......希望Eric如果看到这个就会更新他的帖子.=)

  • 这是更好的答案,因为它正确检查日志级别. (7认同)
  • @pfa如何添加`logging.DEBUG_LEVEL_NUM = 9`,这样您可以在代码中导入记录器的任何地方访问该调试级别? (4认同)
  • 肯定是`DEBUG_LEVEL_NUM = 9`你应该定义`logging.DEBUG_LEVEL_NUM = 9`.通过这种方式,您可以使用`log_instance.setLevel(logging.DEBUG_LEVEL_NUM)`,就像使用正确的`logging.DEBUG`或`logging.INFO`一样 (4认同)
  • 这个答案非常有帮助。谢谢 pfa 和 EricS。我想建议为了完整起见,再包含两个语句: `logging.DEBUGV = DEBUG_LEVELV_NUM` 和 `logging.__all__ += ['DEBUGV']` 第二个并不是非常重要,但如果您有任何代码,第一个是必要的它动态调整日志记录级别,并且您希望能够执行类似“if verbose: logger.setLevel(logging.DEBUGV)”的操作 (3认同)
  • 当然比目前的答案更有用. (2认同)
  • 因为这是添加到现有类中的运行时定义,您知道是否有办法让 Intellij / Pycharm 发现这一点并允许自动完成? (2认同)

小智 59

我接受了"避免看到lambda"的答案,并且必须修改log_at_my_log_level的添加位置.我也看到了保罗所做的问题"我觉得这不行.你不需要logger作为log_at_my_log_level中的第一个arg吗?" 这对我有用

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv
Run Code Online (Sandbox Code Playgroud)

  • +1也是.优雅的方法,它完美地工作.重要提示:***您只需在一个模块中执行一次,它将适用于所有模块***.您甚至不必导入"设置"模块.所以把它放在一个包的`__init __.py`中并且开心:D (7认同)
  • @Eric S.您应该看一下这个答案:http://stackoverflow.com/a/13638084/600110 (4认同)
  • 这个答案对我不起作用。尝试执行“logging.debugv”会出现错误“AttributeError:模块“logging”没有属性“debugv”” (3认同)

Mad*_*ist 45

将所有现有答案与大量使用经验相结合,我认为我已经提出了所有需要完成的事项列表,以确保完全无缝地使用新级别.以下步骤假定您要添加TRACE具有值的新级别logging.DEBUG - 5 == 5:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') 需要调用以获取内部注册的新级别,以便可以按名称引用它.
  2. logging为了保持一致性,需要将新级别作为属性添加到自身中:logging.TRACE = logging.DEBUG - 5.
  3. trace需要将一个调用的方法添加到logging模块中.它应该表现得就像debug,info等等.
  4. trace需要将一个调用的方法添加到当前配置的记录器类中.由于这不是100%保证logging.Logger,logging.getLoggerClass()而是使用.

所有步骤均在以下方法中说明:

def addLoggingLevel(levelName, levelNum, methodName=None):
    """
    Comprehensively adds a new logging level to the `logging` module and the
    currently configured logging class.

    `levelName` becomes an attribute of the `logging` module with the value
    `levelNum`. `methodName` becomes a convenience method for both `logging`
    itself and the class returned by `logging.getLoggerClass()` (usually just
    `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
    used.

    To avoid accidental clobberings of existing attributes, this method will
    raise an `AttributeError` if the level name is already an attribute of the
    `logging` module or if the method name is already present 

    Example
    -------
    >>> addLoggingLevel('TRACE', logging.DEBUG - 5)
    >>> logging.getLogger(__name__).setLevel("TRACE")
    >>> logging.getLogger(__name__).trace('that worked')
    >>> logging.trace('so did this')
    >>> logging.TRACE
    5

    """
    if not methodName:
        methodName = levelName.lower()

    if hasattr(logging, levelName):
       raise AttributeError('{} already defined in logging module'.format(levelName))
    if hasattr(logging, methodName):
       raise AttributeError('{} already defined in logging module'.format(methodName))
    if hasattr(logging.getLoggerClass(), methodName):
       raise AttributeError('{} already defined in logger class'.format(methodName))

    # This method was inspired by the answers to Stack Overflow post
    # http://stackoverflow.com/q/2183233/2988730, especially
    # http://stackoverflow.com/a/13638084/2988730
    def logForLevel(self, message, *args, **kwargs):
        if self.isEnabledFor(levelNum):
            self._log(levelNum, message, *args, **kwargs)
    def logToRoot(message, *args, **kwargs):
        logging.log(levelNum, message, *args, **kwargs)

    logging.addLevelName(levelNum, levelName)
    setattr(logging, levelName, levelNum)
    setattr(logging.getLoggerClass(), methodName, logForLevel)
    setattr(logging, methodName, logToRoot)
Run Code Online (Sandbox Code Playgroud)


Wis*_*ind 36

这个问题相当陈旧,但我刚刚处理了相同的主题,并找到了一种类似于已经提到过的方法,这对我来说似乎有点清洁.这是在3.4上测试的,所以我不确定使用的方法是否存在于旧版本中:

from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET

VERBOSE = 5

class MyLogger(getLoggerClass()):
    def __init__(self, name, level=NOTSET):
        super().__init__(name, level)

        addLevelName(VERBOSE, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        if self.isEnabledFor(VERBOSE):
            self._log(VERBOSE, msg, args, **kwargs)

setLoggerClass(MyLogger)
Run Code Online (Sandbox Code Playgroud)

  • @MarcoSulla它们被记录为Python日志记录模块的一部分。我假设使用动态子类,以防有人在使用此库时想要自己的llogger。然后,将MyLogger合并为我的类的子类。 (3认同)

Der*_*Weh 20

虽然我们已经有很多正确的答案,但在我看来,以下更像是 Pythonic:

import logging

from functools import partial, partialmethod

logging.TRACE = 5
logging.addLevelName(logging.TRACE, 'TRACE')
logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE)
logging.trace = partial(logging.log, logging.TRACE)
Run Code Online (Sandbox Code Playgroud)

如果你想mypy在你的代码上使用,建议添加# type: ignore禁止添加属性的警告。

  • 看起来不错,但最后一行令人困惑。不应该是“logging.trace =partial(logging.log,logging.TRACE)#type:ignore”吗? (2认同)

sch*_*mar 19

谁开始使用内部方法(self._log)的不良做法,为什么每个答案都基于此?!pythonic解决方案将使用,self.log所以你不必乱用任何内部的东西:

import logging

SUBDEBUG = 5
logging.addLevelName(SUBDEBUG, 'SUBDEBUG')

def subdebug(self, message, *args, **kws):
    self.log(SUBDEBUG, message, *args, **kws) 
logging.Logger.subdebug = subdebug

logging.basicConfig()
l = logging.getLogger()
l.setLevel(SUBDEBUG)
l.subdebug('test')
l.setLevel(logging.DEBUG)
l.subdebug('test')
Run Code Online (Sandbox Code Playgroud)

  • 需要使用_log()而不是log()来避免在调用堆栈中引入额外的级别.如果使用log(),引入额外的堆栈帧会导致多个LogRecord属性(funcName,lineno,filename,pathname,...)指向调试函数而不是实际的调用者.这可能不是理想的结果. (18认同)
  • 从什么时候开始调用类自己的内部方法是不允许的?仅仅因为函数在类之外定义并不意味着它是一个外部方法. (5认同)
  • 此方法不仅不必要地更改堆栈跟踪,而且还不检查是否记录了正确的级别. (3认同)

LtP*_*ack 9

我发现为传递log()函数的logger对象创建一个新属性更容易.我认为logger模块提供addLevelName()和log()就是出于这个原因.因此,不需要子类或新方法.

import logging

@property
def log(obj):
    logging.addLevelName(5, 'TRACE')
    myLogger = logging.getLogger(obj.__class__.__name__)
    setattr(myLogger, 'trace', lambda *args: myLogger.log(5, *args))
    return myLogger
Run Code Online (Sandbox Code Playgroud)

现在

mylogger.trace('This is a trace message')
Run Code Online (Sandbox Code Playgroud)

应该按预期工作.


Nou*_*him 8

我认为你必须对类进行子Logger类化并添加一个方法trace,该方法基本上调用Logger.log低于的级别DEBUG.我没试过这个,但这就是文档所指出的.

  • @ S.Lott - 实际上(至少在目前的Python版本中,可能不是2010年的情况)你必须使用[`setLoggerClass(MyClass)`](https://docs.python.org/3 /library/logging.html?highlight=logging#logging.setLoggerClass)然后正常调用`getLogger()`... (4认同)
  • 你可能想要替换`logging.getLogger`来返回你的子类而不是内置类. (3认同)

Gri*_*ave 5

这对我有用:

import logging
logging.basicConfig(
    format='  %(levelname)-8.8s %(funcName)s: %(message)s',
)
logging.NOTE = 32  # positive yet important
logging.addLevelName(logging.NOTE, 'NOTE')      # new level
logging.addLevelName(logging.CRITICAL, 'FATAL') # rename existing

log = logging.getLogger(__name__)
log.note = lambda msg, *args: log._log(logging.NOTE, msg, args)
log.note('school\'s out for summer! %s', 'dude')
log.fatal('file not found.')
Run Code Online (Sandbox Code Playgroud)

正如 @marqueed 指出的那样,lambda/funcName 问题已使用 logger._log 修复。我认为使用 lambda 看起来更干净一些,但缺点是它不能接受关键字参数。我自己从来没有用过,所以没什么大不了的。

  注意设置:学校放暑假了!老兄
  致命设置:找不到文件。


Bry*_*nta 5

创建自定义记录器的提示:

  1. 不要使用_log,使用log(您不必检查isEnabledFor
  2. 日志记录模块应该是自定义记录器的一个创建实例,因为它在中getLogger起到了一些神奇作用,因此您需要通过以下方式设置类setLoggerClass
  3. __init__如果您不存储任何内容,则无需为记录器定义类
# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
    def trace(self, msg, *args, **kwargs):
        self.log(TRACE, msg, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

调用此记录器时,请使用setLoggerClass(MyLogger)它作为默认记录器getLogger

logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
Run Code Online (Sandbox Code Playgroud)

您需要setFormattersetHandler以及setLevel(TRACE)handler与对log自身实际SE这低水平跟踪