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

我想为我的应用程序使用loglevel TRACE(5),因为我不认为debug()是足够的。 另外log(5, msg)是不是我想要的。 我如何添加一个自定义的loglevel到Pythonlogging器?

我有一个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的是,恕我直言,一个很好的解决scheme基于非常好的build议从埃里克S。

@Eric S.

Eric S.的答案非常好,但是我通过实验了解到,无论日志级别设置如何,总是会导致在新的debugging级别上logging消息。 因此,如果您创build一个新的级别号码9,如果您调用setLevel(50),则较低级别的消息将被错误地打印。 为了防止这种情况发生,您需要在“debugv”函数中另外一行来检查是否实际上启用了所讨论的日志logging级别。

修正了日志级别是否被启用的例子:

 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'. if self.isEnabledFor(DEBUG_LEVELV_NUM): self._log(DEBUG_LEVELV_NUM, message, args, **kws) logging.Logger.debugv = debugv 

如果您在Python 2.7的logging.__init__.py查看class Logger的代码,那么这就是所有标准日志函数所做的(.critical,.debug等)。

我显然不能在缺乏声誉的情况下回复他人的答复……希望埃里克能够更新他的post,如果他看到这个。 =)

我采取了“避免看到lambda”的答案,并不得不修改在哪里添加log_at_my_log_level。 我也看到了Paul的问题,“我不认为这是有效的。你不需要logger作为log_at_my_log_level中的第一个参数吗? 这对我有效

 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.4testing,所以我不确定所用的方法是否存在于旧版本中:

 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) 

谁开始使用内部方法的糟糕做法( self._log ),为什么每个答案都基于这个? self.log解决scheme将是使用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') 

我发现为传递log()函数的logging器对象创build一个新的属性更容易。 我认为logging器模块提供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') 

应该按预期工作。

结合所有现有的答案和大量的使用经验,我想我已经拿出了所有需要做的事情的清单,以确保完全无缝地使用新的水平。 下面的步骤假定你正在添加一个新的级别TRACElogging.DEBUG - 5 == 5

  1. 需要调用logging.addLevelName(logging.DEBUG - 5, 'TRACE')来获取内部注册的新级别,以便可以按名称引用它。
  2. 新的级别需要添加为一个属性来logging自身的一致性: logging.TRACE = logging.DEBUG - 5
  3. 需要将一个名为trace的方法添加到logging模块中。 它应该像debuginfo等一样
  4. 一个名为trace的方法需要被添加到当前configuration的logging器类中。 因为这不是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) 

我想你将不得不Logger类,并添加一个名为trace的方法,它基本上调用Logger.log ,其级别低于DEBUG 。 我没有尝试过,但是这是文档所示 。

根据我的经验,这是op的问题的完整解决scheme…为了避免将“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 

我从来没有尝试过独立的logging类,但我认为基本的想法是一样的(使用_log)。

这对我工作:

 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看起来有点干净,但缺点是不能使用关键字参数。 我从来没有用过,所以没有biggie。

  注释设置:学校夏天出去! 花花公子
  致命设置:找不到文件。

创build自定义logging器的提示:

  1. 不要使用_log ,使用log (你不必检查isEnabledFor
  2. 日志logging模块应该是创build自定义logging器的实例,因为它在getLogger中有一些魔力,所以你需要通过setLoggerClass来设置类
  3. 如果不存储任何内容,则不需要为logging器类定义__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) 

当调用这个logging器时,使用setLoggerClass(MyLogger)将它作为getLogger的默认logging器

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

您需要在handlerlog本身上设置setFormattersetHandlersetLevel(TRACE)以实际select此低级别跟踪

除了疯狂的物理学家的例子,以获得文件名和行号正确:

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

作为向Logger类添加额外方法的替代方法,我build议使用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') 

如果有人想要一种自动的方式来dynamic地将日志logging级别添加到日志logging模块(或其副本),我已经创build了这个函数,扩展@ 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() exec('custom_log_module.Logger.'+eval('log_call')+' = cust_log', None, locals()) return custom_log_module