[英]How to add a custom loglevel to Python's logging facility
I'd like to have loglevel TRACE (5) for my application, as I don't think that debug()
is sufficient.我想为我的应用程序设置日志级别跟踪 (5),因为我认为
debug()
不够。 Additionally log(5, msg)
isn't what I want.另外
log(5, msg)
不是我想要的。 How can I add a custom loglevel to a Python logger?如何将自定义日志级别添加到 Python 记录器?
I've a mylogger.py
with the following content:我有一个包含以下内容的
mylogger.py
:
import logging
@property
def log(obj):
myLogger = logging.getLogger(obj.__class__.__name__)
return myLogger
In my code I use it in the following way:在我的代码中,我按以下方式使用它:
class ExampleClass(object):
from mylogger import log
def __init__(self):
'''The constructor with the logger'''
self.log.debug("Init runs")
Now I'd like to call self.log.trace("foo bar")
现在我想调用
self.log.trace("foo bar")
Thanks in advance for your help.在此先感谢您的帮助。
Edit (Dec 8th 2016): I changed the accepted answer to pfa's which is, IMHO, an excellent solution based on the very good proposal from Eric S.编辑(2016 年 12 月 8 日):我将接受的答案更改为pfa ,恕我直言,这是一个基于 Eric S 的非常好的建议的出色解决方案。
@Eric S. @埃里克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. Eric S. 的回答非常好,但我通过实验了解到,这将始终导致打印在新调试级别记录的消息——无论日志级别设置为什么。 So if you make a new level number of
9
, if you call setLevel(50)
, the lower level messages will erroneously be printed.因此,如果您将新级别编号设为
9
,如果您调用setLevel(50)
,则会错误地打印较低级别的消息。
To prevent that from happening, you need another line inside the "debugv" function to check if the logging level in question is actually enabled.
为了防止这种情况发生,您需要在“debugv”函数中使用另一行来检查所讨论的日志记录级别是否实际启用。
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.).如果您查看 Python 2.7 的
logging.__init__.py
class Logger
的代码,这就是所有标准日志函数所做的(.critical、.debug 等)。
I apparently can't post replies to others' answers for lack of reputation... hopefully Eric will update his post if he sees this.由于缺乏声誉,我显然无法回复其他人的答案......希望 Eric 看到这个后会更新他的帖子。 =)
=)
Combining all of the existing answers with a bunch of usage experience, I think that I have come up with a list of all the things that need to be done to ensure completely seamless usage of the new level.将所有现有答案与一堆使用经验相结合,我想我已经列出了需要完成的所有事情的列表,以确保完全无缝地使用新级别。 The steps below assume that you are adding a new level
TRACE
with value logging.DEBUG - 5 == 5
:以下步骤假设您正在添加一个新级别的
TRACE
,其值为logging.DEBUG - 5 == 5
:
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
needs to be invoked to get the new level registered internally so that it can be referenced by name.logging.addLevelName(logging.DEBUG - 5, 'TRACE')
来获取内部注册的新级别,以便可以通过名称引用它。logging
itself for consistency: logging.TRACE = logging.DEBUG - 5
.logging
本身以保持一致性: logging.TRACE = logging.DEBUG - 5
。trace
needs to be added to the logging
module.logging
模块中添加一个名为trace
的方法。 It should behave just like debug
, info
, etc.debug
、 info
等。trace
needs to be added to the currently configured logger class.trace
的方法。 Since this is not 100% guaranteed to be logging.Logger
, use logging.getLoggerClass()
instead.logging.Logger
,请改用logging.getLoggerClass()
。 All the steps are illustrated in the method below:所有步骤都在下面的方法中说明:
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)
I took the avoid seeing "lambda" answer and had to modify where the log_at_my_log_level
was being added.我采取了避免看到“lambda”的答案,不得不修改添加
log_at_my_log_level
。 I too saw the problem that Paul did – I don't think this works.我也看到了保罗所做的问题——我认为这行不通。 Don't you need logger as the first arg in
log_at_my_log_level
?您不需要 logger 作为
log_at_my_log_level
的第一个参数吗? This worked for me这对我有用
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
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:
这是在 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)
Who started the bad practice of using internal methods ( self._log
) and why is each answer based on that?!谁开始使用内部方法(
self._log
)的不良做法,为什么每个答案都基于此?! The pythonic solution would be to use self.log
instead so you don't have to mess with any internal stuff: self.log
解决方案是使用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')
While we have already plenty of correct answers, the following is in my opinion more pythonic:虽然我们已经有很多正确的答案,但在我看来,以下更像是 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)
If you want to use mypy
on your code, it is recommended to add # type: ignore
to suppress warnings from adding attribute.如果你想在你的代码上使用
mypy
,建议添加mypy
# type: ignore
以抑制添加属性的警告。
I think you'll have to subclass the Logger
class and add a method called trace
which basically calls Logger.log
with a level lower than DEBUG
.我认为您必须
Logger
类并添加一个名为trace
的方法,该方法基本上调用Logger.log
的级别低于DEBUG
。 I haven't tried this but this is what the docs indicate .我没有试过这个,但这就是文档所表明的。
I find it easier to create a new attribute for the logger object that passes the log() function.我发现为传递 log() 函数的记录器对象创建一个新属性更容易。 I think the logger module provides the addLevelName() and the log() for this very reason.
我认为记录器模块正是因为这个原因提供了 addLevelName() 和 log() 。 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.应该按预期工作。
Tips for creating a custom logger:创建自定义记录器的提示:
_log
, use log
(you don't have to check isEnabledFor
)_log
,使用log
(你不必检查isEnabledFor
)getLogger
, so you will need to set the class via setLoggerClass
getLogger
中getLogger
一些getLogger
,因此您需要通过setLoggerClass
设置该类__init__
for the logger, class if you are not storing anything__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)
When calling this logger use setLoggerClass(MyLogger)
to make this the default logger from getLogger
当我们使用这个记录仪使用
setLoggerClass(MyLogger)
使这个从默认记录getLogger
logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
You will need to setFormatter
, setHandler
, and setLevel(TRACE)
on the handler
and on the log
itself to actually se this low level trace您需要在
handler
和log
本身上设置setFormatter
、 setHandler
和setLevel(TRACE)
以实际设置此低级别跟踪
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.正如@marqueed 指出的那样,lambda/funcName 问题已通过 logger._log 修复。 I think using lambda looks a bit cleaner, but the drawback is that it can't take keyword arguments.
我认为使用 lambda 看起来更简洁,但缺点是它不能接受关键字参数。 I've never used that myself, so no biggie.
我自己从来没有用过,所以没什么大不了的。
NOTE setup: school's out for summer! dude FATAL setup: file not found.
Addition to Mad Physicists example to get file name and line number correct:除了 Mad Physicists 示例以获取正确的文件名和行号:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
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:根据我的经验,这是操作问题的完整解决方案......为了避免将“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
I've never tried working with a standalone logger class, but I think the basic idea is the same (use _log).我从未尝试过使用独立的记录器类,但我认为基本思想是相同的(使用 _log)。
based on pinned answer, i wrote a little method which automaticaly create new logging levels基于固定答案,我写了一个小方法,它自动创建新的日志记录级别
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))
config may smth like that:配置可能是这样的:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
Someone might wanna do, a root level custom logging;有人可能想做,根级自定义日志记录; and avoid the usage of logging.get_logger(''):
并避免使用 logging.get_logger(''):
import logging
from datetime import datetime
c_now=datetime.now()
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] :: %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("../logs/log_file_{}-{}-{}-{}.log".format(c_now.year,c_now.month,c_now.day,c_now.hour))
]
)
DEBUG_LEVELV_NUM = 99
logging.addLevelName(DEBUG_LEVELV_NUM, "CUSTOM")
def custom_level(message, *args, **kws):
logging.Logger._log(logging.root,DEBUG_LEVELV_NUM, message, args, **kws)
logging.custom_level = custom_level
# --- --- --- ---
logging.custom_level("Waka")
As alternative to adding an extra method to the Logger class I would recommend using the Logger.log(level, msg)
method.作为向 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')
I'm confused;我很困惑; with python 3.5, at least, it just works:
至少使用 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")
output:输出:
DEBUG:root:y1
调试:root:y1
TRACE:root:y2
跟踪:根:y2
In case anyone wants an automated way to add a new logging level to the logging module (or a copy of it) dynamically, I have created this function, expanding @pfa's answer:如果有人想要一种自动的方式来动态地向日志记录模块(或其副本)添加新的日志记录级别,我已经创建了这个函数,扩展了@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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.