问题:如何在Python的日志记录工具中添加自定义日志级别
我想为我的应用程序使用loglevel TRACE(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的公认答案,即IMHO,这是基于Eric S的非常好的建议的出色解决方案。
回答 0
@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
如果查看Python 2.7中的class Logger
in 代码logging.__init__.py
,这就是所有标准日志功能(.critical,.debug等)的功能。
我显然不能发表因缺乏声誉而对其他人的回答的回复…希望埃里克(Eric)会在看到这一点后更新其帖子。=)
回答 1
我回答了“避免看到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
回答 2
将所有现有答案与大量使用经验相结合,我想我已经列出了确保完全无缝使用新级别所需要做的所有事情的清单。下面的步骤假定您要添加一个TRACE
具有value 的新级别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)
回答 3
这个问题比较老,但是我只是处理相同的主题,并发现了一种与已经提到的类似的方法,对我来说似乎更干净。这已经在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
谁开始使用内部方法(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')
回答 5
我发现为通过log()函数的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')
应该能按预期工作。
回答 6
虽然我们已经有了很多正确的答案,但我认为以下内容更像pythonic:
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)
如果要mypy
在代码上使用,建议添加# type: ignore
以禁止添加属性的警告。
回答 7
我认为您必须对该Logger
类进行子类化,并添加一个称为的方法trace
,该方法基本上Logger.log
以低于的级别进行调用DEBUG
。我还没有尝试过,但这就是文档所指示的。
回答 8
创建自定义记录器的提示:
- 不要使用
_log
,使用log
(您不必检查isEnabledFor
) - 日志记录模块应该是自定义记录器的一个创建实例,因为它在中
getLogger
起到了一些神奇作用,因此您需要通过以下方式设置类setLoggerClass
__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)
它作为默认记录器getLogger
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
您需要setFormatter
,setHandler
以及setLevel(TRACE)
对handler
与对log
自身实际SE这低水平跟踪
回答 9
这对我有用:
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问题已通过@marqueed指出的logger._log解决。我认为使用lambda看起来更干净一些,但是缺点是它不能接受关键字参数。我自己从来没有用过,所以没什么大不了的。
注意设置:暑假就要放学了!花花公子 致命设置:找不到文件。
回答 10
以我的经验,这是操作程序问题的完整解决方案…为了避免看到“ 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
我从未尝试过使用独立的记录器类,但我认为基本思想是相同的(使用_log)。
回答 11
除了“疯狂物理学家”示exceptions,还可以使文件名和行号正确无误:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
回答 12
基于固定的答案,我写了一种方法可以自动创建新的日志记录级别
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,
}
回答 13
作为向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')
回答 14
我很困惑; 至少在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")
输出:
调试:root:y1
跟踪:root:y2
回答 15
万一有人想要一种自动的方式来动态地向日志记录模块(或其副本)添加新的日志记录级别,我创建了此函数,扩展了@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