代码之家  ›  专栏  ›  技术社区  ›  tuergeist

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

  •  85
  • tuergeist  · 技术社区  · 15 年前

    我希望我的应用程序有日志级跟踪(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("foo bar")

    事先谢谢你的帮助。

    编辑 (2016年12月8日):我将接受的答案改为 pfa's 也就是说,imho,基于Eric S的非常好的建议,这是一个很好的解决方案。

    15 回复  |  直到 6 年前
        1
  •  135
  •   Shiplu Mokaddim    6 年前

    @ 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
    

    如果你看代码 class Logger 在里面 logging.__init__.py 对于Python2.7,这是所有标准日志函数所做的工作(关键的,.debug等)。

    很明显我不能回复别人的答案,因为他们缺乏声誉…希望埃里克看到这一点会更新他的帖子。=)

        2
  •  58
  •   Eric S.    12 年前

    我得到了“避免看到lambda”的答案,并且必须修改在“我的日志”级别添加日志的位置。我也看到了保罗的问题,“我认为这行不通。你不需要记录器作为日志中的第一个参数吗?这对我有用

    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
    
        3
  •  32
  •   Wisperwind    10 年前

    这个问题很古老,但我只是处理了同一个话题,发现了一种类似于前面提到的,对我来说似乎更清楚的方法。这是在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)
    
        4
  •  28
  •   Mad Physicist    8 年前

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

    1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') 需要调用以在内部注册新级别,以便可以通过名称引用它。
    2. 需要将新级别作为属性添加到 logging 为了保持一致性: logging.TRACE = logging.DEBUG - 5 .
    3. 称之为方法 trace 需要添加到 登录中 模块。它应该表现得像 debug , info 等。
    4. 调用的方法 追踪 需要添加到当前配置的记录器类中。因为这不是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)
    
        5
  •  17
  •   schlamar    11 年前

    是谁开始使用内部方法的坏做法的?( 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')
    
        6
  •  9
  •   LtPinback    14 年前

    我发现为传递logger()函数的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
    

    现在

    mylogger.trace('This is a trace message')
    

    应该按预期工作。

        7
  •  8
  •   Noufal Ibrahim    15 年前

    我想你得把 Logger 类并添加名为 trace 这基本上就是 Logger.log 水平低于 DEBUG . 我没试过,但这就是 docs indicate .

        8
  •  4
  •   Bryce Guinta    8 年前

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

    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)
    

    调用此记录器时,请使用 setLoggerClass(MyLogger) 使其成为默认记录器 获得记录器

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

    你需要 setFormatter , setHandler ,和 setLevel(TRACE) handler 以及关于 日志 它本身实际上就是这个低级别的跟踪

        9
  •  3
  •   Gringo Suave    12 年前

    这对我很有用:

    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 as@marqueed指出。我认为使用lambda看起来有点干净,但缺点是它不能接受关键字参数。我自己从来没有用过,所以没什么大不了的。

      NOTE     setup: school's out for summer! dude
      FATAL    setup: file not found.
    
        10
  •  2
  •   marqueed    13 年前

    以我的经验来看,这是全面的解决方案——手术的问题……要避免将“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类,但我认为基本思想是相同的(使用日志)。

        11
  •  2
  •   Frederik Holljen    8 年前

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

    def logToRoot(message, *args, **kwargs):
        if logging.root.isEnabledFor(levelNum):
            logging.root._log(levelNum, message, args, **kwargs)
    
        12
  •  0
  •   schlamar    11 年前

    作为向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')
    
        13
  •  0
  •   gerardw    6 年前

    我很困惑;至少对于python 3.5来说,它是有效的:

    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")
    

    输出:

    调试:根:Y1

    痕迹:根:Y2

        14
  •  0
  •   groshevpavel    6 年前

    基于固定答案, 我写了一个自动创建新日志级别的小方法

    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,
    }
    
        15
  •  -3
  •   Vasilis Lemonidis    6 年前

    如果有人想要一种自动方式动态地向日志模块(或其副本)添加新的日志级别,我已经创建了这个函数,扩展了@pfa的答案:

    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