How to add a custom loglevel to Python's logging facility

前端 未结 16 2244
旧时难觅i
旧时难觅i 2020-11-27 09:54

I\'d like to have loglevel TRACE (5) for my application, as I don\'t think that debug() is sufficient. Additionally log(5, msg) isn\'t what I want.

相关标签:
16条回答
  • 2020-11-27 10:13

    I find it easier to create a new attribute for the logger object that passes the log() function. I think the logger module provides the addLevelName() and the log() for this very reason. Thus no subclasses or new method needed.

    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
    

    now

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

    should work as expected.

    0 讨论(0)
  • 2020-11-27 10:14

    This worked for me:

    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.')
    

    The lambda/funcName issue is fixed with logger._log as @marqueed pointed out. I think using lambda looks a bit cleaner, but the drawback is that it can't take keyword arguments. I've never used that myself, so no biggie.

      NOTE     setup: school's out for summer! dude
      FATAL    setup: file not found.
    
    0 讨论(0)
  • 2020-11-27 10:20

    @Eric S.

    Eric S.'s answer is excellent, but I learned by experimentation that this will always cause messages logged at the new debug level to be printed -- regardless of what the log level is set to. So if you make a new level number of 9, if you call setLevel(50), the lower level messages will erroneously be printed.

    To prevent that from happening, you need another line inside the "debugv" function to check if the logging level in question is actually enabled.

    Fixed example that checks if the logging level is enabled:

    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
    

    If you look at the code for class Logger in logging.__init__.py for Python 2.7, this is what all the standard log functions do (.critical, .debug, etc.).

    I apparently can't post replies to others' answers for lack of reputation... hopefully Eric will update his post if he sees this. =)

    0 讨论(0)
  • 2020-11-27 10:20

    Who started the bad practice of using internal methods (self._log) and why is each answer based on that?! The pythonic solution would be to use self.log instead so you don't have to mess with any internal stuff:

    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')
    
    0 讨论(0)
  • 2020-11-27 10:21

    This question is rather old, but I just dealt with the same topic and found a way similiar to those already mentioned which appears a little cleaner to me. This was tested on 3.4, so I'm not sure whether the methods used exist in older versions:

    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)
    
    0 讨论(0)
  • 2020-11-27 10:21

    In my experience, this is the full solution the the op's problem... to avoid seeing "lambda" as the function in which the message is emitted, go deeper:

    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
    

    I've never tried working with a standalone logger class, but I think the basic idea is the same (use _log).

    0 讨论(0)
提交回复
热议问题