Подтвердить что ты не робот

Как добавить пользовательский лог-уровень в средство ведения журнала 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")

Заранее благодарим за помощь.

Изменить (8 декабря 2016 года): я изменил принятый ответ на pfa, который является IMHO, отличным решением, основанным на самом хорошее предложение от Эрика С.

4b9b3361

Ответ 1

@Эрик С.

Ответ на 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

Если вы посмотрите на код для class Logger в logging.__init__.py для Python 2.7, это то, что делают все стандартные функции журнала (.critical,.debug и т.д.).

Я, по-видимому, не могу публиковать ответы на чужие ответы из-за отсутствия репутации... надеюсь, Эрик обновит свой пост, если увидит это. знак равно

Ответ 2

Я принял ответ "не видеть лямбда" и должен был изменить, где добавляется 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

Ответ 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

Объединяя все существующие ответы с кучей опыта использования, я думаю, что у меня есть список всех вещей, которые нужно сделать для обеспечения полного использования нового уровня. В приведенных ниже шагах предположим, что вы добавляете новый уровень TRACE со значением logging.DEBUG - 5 == 5:

  • logging.addLevelName(logging.DEBUG - 5, 'TRACE') необходимо вызвать, чтобы получить новый уровень, зарегистрированный внутри, чтобы на него можно было ссылаться по имени.
  • Новый уровень должен быть добавлен как атрибут для logging для согласованности: logging.TRACE = logging.DEBUG - 5.
  • В модуль logging необходимо добавить метод TRACE. Он должен вести себя так же, как 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 Qaru 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)

Ответ 5

Кто начал плохую практику использования внутренних методов (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')

Ответ 6

Мне легче создать новый атрибут для объекта logger, который передает функцию 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')

должен работать как ожидалось.

Ответ 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, чтобы на самом деле выполнить эту трассировку низкого уровня

Ответ 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\ out for summer! %s', 'dude')
log.fatal('file not found.')

Проблема лямбда /funcName исправлена ​​с помощью logger._log, как указано в @marqueed. Я думаю, что использование лямбда выглядит немного чище, но недостатком является то, что он не может принимать аргументы ключевых слов. Я никогда не пользовался этим сам, так что не biggie.

  NOTE     setup: school out for summer! dude
  FATAL    setup: file not found.

Ответ 10

По моему опыту, это полное решение проблемы op... чтобы не видеть "лямбда" в качестве функции, в которой излучается сообщение, идите глубже:

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

Я никогда не пробовал работать с автономным классом logger, но я думаю, что основная идея одна и та же (используйте _log).

Ответ 11

Добавьте пример Mad Physicists, чтобы получить правильное имя файла и номер строки:

def logToRoot(message, *args, **kwargs):
    if logging.root.isEnabledFor(levelNum):
        logging.root._log(levelNum, message, args, **kwargs)

Ответ 12

В качестве альтернативы добавлению дополнительного метода в класс 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')

Ответ 13

Я не совсем понимаю; с 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")

выход:

DEBUG: корень: y1

TRACE: корень: y2

Ответ 14

на основе закрепленного ответа я написал небольшой метод, который автоматически создает новые уровни ведения журнала

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 why it 21, 22, etc..
    "FOO":      21,
    "BAR":      22,
}

Ответ 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