如何添加一个自定义的日志级别对蟒蛇的记录工具
-
24-09-2019 - |
题
我想有日志级别跟踪(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")
在此先感谢您的帮助。
编辑 (Dec第8 2016年):我改变了所接受的答案 pfa的 这是,恕我直言,一个优秀的解决方案基于非常好的建议,从Eric S.
解决方案
@Eric S上。
埃里克·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
如果你看一下在class Logger
logging.__init__.py
的Python 2.7的代码,这是所有标准的日志功能做(.critical,的.debug等)。
我显然不能进行回复别人的答案,缺乏信誉的...希望埃里克将会如果他看到这个更新了他的岗位。 =)
其他提示
我把“避免看到拉姆达”的答案,不得不修改添加的log_at_my_log_level在哪里。我也看到了这个问题,保罗没有“我不认为这个工程。你不需要记录在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
结合所有现有的答案有一堆的使用情况的经验,我认为我已经想出了一个列表中的所有事情需要做,以确保完全无缝使用的新的水平。下面的步骤,假设你是添加一个新的水平 TRACE
有价值 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.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)
谁开始使用内部方法(self._log
)为什么是基于每个答案的坏习惯?在Python的解决办法是使用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')
我发现很容易创建用于传递日志()函数记录器对象的新属性。我想记录器模块提供了addLevelName()和日志()出于这个原因。因此,没有亚类或新方法所需的。
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')
如预期应该工作。
我想你得子类Logger
类,并添加一个方法叫做trace
基本上与水平比Logger.log
降低通话DEBUG
。我没有试过,但是这是在文档表明。
提示创建一个自定义记录器:
- 请勿使用
_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这种低级跟踪
这为我工作:
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.')
如@marqueed指出拉姆达/了funcName问题被固定logger._log。我认为使用lambda看起来有点清洁,但缺点是它不能采取关键字参数。我从未使用过自己,所以根本不算什么。
NOTE setup: school's out for summer! dude FATAL setup: file not found.
在我的经验,这是一个完整的解决方案的运算的问题... ...,以避免看到“拉姆达”在其发出的邮件功能,更深入了解:
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)。
加成疯狂物理学家示例获取文件名和行号正确的:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
虽然我们有很多已经是正确的答案,以下是在我看来,更Python:
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
从添加属性添加禁止警告。
作为替代方案增加额外的方法来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')
我混淆;用蟒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")
输出:
DEBUG:根:Y1
TRACE:根:Y2
基于被钉扎的答案, 我写一点点方法,其automaticaly创建新的记录级别
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,
}
在情况下,任何人想要一个自动化的方式来添加新的日志级别记录模块(或它的副本)动态,我创造了这个功能,扩大@ 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