如何将自定义的日志级别添加到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')
应该按预期工作。
结合所有现有的答案和大量的使用经验,我想我已经拿出了所有需要做的事情的清单,以确保完全无缝地使用新的水平。 下面的步骤假定你正在添加一个新的级别TRACE
值logging.DEBUG - 5 == 5
:
- 需要调用
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
来获取内部注册的新级别,以便可以按名称引用它。 - 新的级别需要添加为一个属性来
logging
自身的一致性:logging.TRACE = logging.DEBUG - 5
。 - 需要将一个名为
trace
的方法添加到logging
模块中。 它应该像debug
,info
等一样 - 一个名为
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器的提示:
- 不要使用
_log
,使用log
(你不必检查isEnabledFor
) - 日志logging模块应该是创build自定义logging器的实例,因为它在
getLogger
中有一些魔力,所以你需要通过setLoggerClass
来设置类 - 如果不存储任何内容,则不需要为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")
您需要在handler
和log
本身上设置setFormatter
, setHandler
和setLevel(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