Как добавить собственный уровень логирования в средство ведения журналов Python - PullRequest
93 голосов
/ 02 февраля 2010

Я бы хотел иметь уровень логики 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's , который, ИМХО, является отличным решением, основанным на очень хорошем предложении Эрика С.

Ответы [ 16 ]

140 голосов
/ 30 ноября 2012

@ Эрик С.

Ответ Эрика С. превосходен, но я экспериментально выяснил, что это всегда приведет к печати сообщений, зарегистрированных на новом уровне отладки, независимо от уровня журнала.установлен в.Поэтому, если вы сделаете новый номер уровня 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 и т. Д.).

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

58 голосов
/ 03 августа 2012

Я взял ответ «избегать видеть лямбду» и должен был изменить место добавления 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
36 голосов
/ 04 марта 2016

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

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

Этот вопрос довольно старый, но я только что затронул ту же тему и нашел способ, похожий на уже упомянутый, который кажется мне немного чище. Это было протестировано на 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)
19 голосов
/ 06 июня 2013

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

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

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

8 голосов
/ 02 февраля 2010

Я думаю, вам придется создать подкласс класса Logger и добавить метод с именем trace, который в основном вызывает Logger.log с уровнем ниже DEBUG. Я не пробовал это, но это то, что в документах указано .

5 голосов
/ 18 августа 2016

Советы по созданию собственного регистратора:

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

3 голосов
/ 21 августа 2012

Это сработало для меня:

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 исправлена ​​с помощью logger._log, как указано @marqueed. Я думаю, что использование лямбды выглядит немного чище, но недостатком является то, что он не может принимать аргументы ключевых слов. Я никогда этим не пользовался, так что не бигги.

  NOTE     setup: school's out for summer! dude
  FATAL    setup: file not found.
2 голосов
/ 21 марта 2019

В то время как у нас уже есть много правильных ответов, на мой взгляд, следующее более питонно:

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 для подавления предупреждений от добавления атрибута.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...