如何将自定义日志级别添加到Python';美国的伐木设施

如何将自定义日志级别添加到Python';美国的伐木设施,python,logging,python-logging,Python,Logging,Python Logging,我希望为我的应用程序提供日志级别跟踪(5),因为我认为debug()不够。另外,log(5,msg)不是我想要的。如何向Python记录器添加自定义日志级别 我有一个mylogger.py,包含以下内容: import logging @property def log(obj): myLogger = logging.getLogger(obj.__class__.__name__) return myLogger 在我的代码中,我以以下方式使用它: class Examp

我希望为我的应用程序提供日志级别跟踪(5),因为我认为
debug()
不够。另外,
log(5,msg)
不是我想要的。如何向Python记录器添加自定义日志级别

我有一个
mylogger.py
,包含以下内容:

import logging

@property
def log(obj):
    myLogger = logging.getLogger(obj.__class__.__name__)
    return myLogger
在我的代码中,我以以下方式使用它:

class ExampleClass(object):
    from mylogger import log

    def __init__(self):
        '''The constructor with the logger'''
        self.log.debug("Init runs")
现在我想调用
self.log.trace(“foobar”)

提前感谢你的帮助


编辑(2016年12月8日):我更改了被接受的答案,即,IMHO,这是一个基于Eric S.非常好的建议的优秀解决方案。

我认为您必须对
Logger
类进行子类化,并添加一个名为
trace
的方法,该方法基本上调用
Logger.log
,级别低于
DEBUG
。我还没试过这个,但这就是我想要的

我发现为传递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
现在


按我的经验,这是op问题的完整解决方案。。。要避免将“lambda”视为发送消息的函数,请深入:

MY_LEVEL_NUM = 25
logging.addLevelName(MY_LEVEL_NUM, "MY_LEVEL_NAME")
def log_at_my_log_level(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(MY_LEVEL_NUM, message, args, **kws)
logger.log_at_my_log_level = log_at_my_log_level
我从未尝试过使用独立的logger类,但我认为基本思想是相同的(使用日志)。

我接受了答案,不得不修改在我的日志级别添加日志的位置。我也看到了保罗所面临的问题——我认为这是行不通的。您不需要logger作为我的日志级别的
log\u中的第一个参数吗?这对我有用

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
这对我很有用:

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.')
lambda/funcName问题已通过logger解决。_log正如@marqueed指出的那样。我认为使用lambda看起来更简洁,但缺点是它不能接受关键字参数。我自己从来没用过,所以没什么大不了的

NOTE setup: school's out for summer! dude FATAL setup: file not found. 注意:学校夏天放假了!伙计 致命设置:找不到文件。 @埃里克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
如果您查看Python 2.7的
日志记录中
类记录器
的代码。\uuuu init\uuu.py
,所有标准日志函数都是这样做的(.critical、.debug等)


我显然不能回复别人的答案,因为我的名声不好。。。如果Eric看到这一点,希望他能更新自己的帖子

是谁开始了使用内部方法(
self.\u 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')

作为向Logger类添加额外方法的替代方法,我建议使用
Logger.log(level,msg)
方法

import logging

TRACE = 5
logging.addLevelName(TRACE, 'TRACE')
FORMAT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'


logging.basicConfig(format=FORMAT)
l = logging.getLogger()
l.setLevel(TRACE)
l.log(TRACE, 'trace message')
l.setLevel(logging.DEBUG)
l.log(TRACE, 'disabled trace message')

这个问题已经很老了,但我只是处理了同样的话题,找到了一种与前面提到的类似的方法,这对我来说似乎有点干净。这是在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)

结合所有现有答案和一系列使用经验,我认为我已经列出了确保新级别完全无缝使用所需做的所有事情。以下步骤假设您正在添加一个新的级别
跟踪
,其值为
logging.DEBUG-5==5

  • logging.addLevelName(logging.DEBUG-5,'TRACE')
    需要被调用以在内部注册新级别,以便可以通过名称引用它
  • 需要将新级别作为属性添加到
    logging
    本身以实现一致性:
    logging.TRACE=logging.DEBUG-5
  • 需要将名为
    trace
    的方法添加到
    logging
    模块中。它的行为应该与
    debug
    info
    等类似
  • 需要将名为
    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)
    

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

  • 不要使用
    \u日志
    ,使用
    日志
    (您不必检查
    isEnabledFor
  • 日志模块应该是创建自定义记录器实例的模块,因为它在
    getLogger
    中发挥了一些作用,所以您需要通过
    setLoggerClass
  • 如果没有存储任何内容,则不需要为logger类定义
    \uuuuuu init\uuuuu
  • #低于调试值10
    跟踪=5
    MyLogger类(logging.Logger):
    def跟踪(self、msg、*args、**kwargs):
    self.log(跟踪、消息、*args、**kwargs)
    
    调用此记录器时,请使用
    setLoggerClass(MyLogger)
    使其成为
    getLogger

    logging.setLoggerClass(MyLogger)
    log = logging.getLogger(__name__)
    # ...
    log.trace("something specific")
    

    您需要在
    处理程序
    日志
    本身上的
    设置格式化程序
    设置处理程序
    设置级别(跟踪)
    来实际使用此低级跟踪

    添加到Mad物理学家示例中,以获得正确的文件名和行号:

    def logToRoot(message, *args, **kwargs):
        if logging.root.isEnabledFor(levelNum):
            logging.root._log(levelNum, message, args, **kwargs)
    

    如果有人想要一种自动方式来动态地向日志模块(或其副本)添加新的日志级别,我已经创建了这个函数,扩展了@pfa的答案:<
    def logToRoot(message, *args, **kwargs):
        if logging.root.isEnabledFor(levelNum):
            logging.root._log(levelNum, message, args, **kwargs)
    
    def add_level(log_name,custom_log_module=None,log_num=None,
                    log_call=None,
                       lower_than=None, higher_than=None, same_as=None,
                  verbose=True):
        '''
        Function to dynamically add a new log level to a given custom logging module.
        <custom_log_module>: the logging module. If not provided, then a copy of
            <logging> module is used
        <log_name>: the logging level name
        <log_num>: the logging level num. If not provided, then function checks
            <lower_than>,<higher_than> and <same_as>, at the order mentioned.
            One of those three parameters must hold a string of an already existent
            logging level name.
        In case a level is overwritten and <verbose> is True, then a message in WARNING
            level of the custom logging module is established.
        '''
        if custom_log_module is None:
            import imp
            custom_log_module = imp.load_module('custom_log_module',
                                                *imp.find_module('logging'))
        log_name = log_name.upper()
        def cust_log(par, message, *args, **kws):
            # Yes, logger takes its '*args' as 'args'.
            if par.isEnabledFor(log_num):
                par._log(log_num, message, args, **kws)
        available_level_nums = [key for key in custom_log_module._levelNames
                                if isinstance(key,int)]
    
        available_levels = {key:custom_log_module._levelNames[key]
                                 for key in custom_log_module._levelNames
                                if isinstance(key,str)}
        if log_num is None:
            try:
                if lower_than is not None:
                    log_num = available_levels[lower_than]-1
                elif higher_than is not None:
                    log_num = available_levels[higher_than]+1
                elif same_as is not None:
                    log_num = available_levels[higher_than]
                else:
                    raise Exception('Infomation about the '+
                                    'log_num should be provided')
            except KeyError:
                raise Exception('Non existent logging level name')
        if log_num in available_level_nums and verbose:
            custom_log_module.warn('Changing ' +
                                      custom_log_module._levelNames[log_num] +
                                      ' to '+log_name)
        custom_log_module.addLevelName(log_num, log_name)
    
        if log_call is None:
            log_call = log_name.lower()
    
        setattr(custom_log_module.Logger, log_call, cust_log)
        return custom_log_module
    
    import logging
    
    
    TRACE = 5
    """more detail than debug"""
    
    logging.basicConfig()
    logging.addLevelName(TRACE,"TRACE")
    logger = logging.getLogger('')
    logger.debug("n")
    logger.setLevel(logging.DEBUG)
    logger.debug("y1")
    logger.log(TRACE,"n")
    logger.setLevel(TRACE)
    logger.log(TRACE,"y2")
        
    
    def set_custom_logging_levels(config={}):
        """
            Assign custom levels for logging
                config: is a dict, like
                {
                    'EVENT_NAME': EVENT_LEVEL_NUM,
                }
            EVENT_LEVEL_NUM can't be like already has logging module
            logging.DEBUG       = 10
            logging.INFO        = 20
            logging.WARNING     = 30
            logging.ERROR       = 40
            logging.CRITICAL    = 50
        """
        assert isinstance(config, dict), "Configuration must be a dict"
    
        def get_level_func(level_name, level_num):
            def _blank(self, message, *args, **kws):
                if self.isEnabledFor(level_num):
                    # Yes, logger takes its '*args' as 'args'.
                    self._log(level_num, message, args, **kws) 
            _blank.__name__ = level_name.lower()
            return _blank
    
        for level_name, level_num in config.items():
            logging.addLevelName(level_num, level_name.upper())
            setattr(logging.Logger, level_name.lower(), get_level_func(level_name, level_num))
    
    
    new_log_levels = {
        # level_num is in logging.INFO section, that's why it 21, 22, etc..
        "FOO":      21,
        "BAR":      22,
    }
    
    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)