我想有日志级别跟踪(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:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') 需要用来获得新的水平,登记的境内,以便它可以引用的名称。
  2. 新的水平,需要添加作为一个属性, logging 本身的一致性: logging.TRACE = logging.DEBUG - 5.
  3. 一方法叫 trace 需要加入 logging 模块。它应该表现得就像 debug, info, 等等。
  4. 一方法叫 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。我没有试过,但是这是在文档表明

提示创建一个自定义记录器:

  1. 请勿使用 _log, 使用 log (你没有检查 isEnabledFor)
  2. 记录模块应该是创建一个实例中的定义记录器,因为它没有的一些魔法 getLogger, 所以你需要设置的类通过 setLoggerClass
  3. 你不需要定义 __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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top