2010-02-02 2 views
70

Я хотел бы иметь loglevel TRACE (5) для моего приложения, так как я не думаю, что достаточно debug(). Кроме того, log(5, msg) не то, что я хочу. Как добавить пользовательский loglevel в регистратор Python?Как добавить пользовательский loglevel в средство ведения журнала 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, который, имхо, отличное решение, основанное на очень хорошее предложение от Eric S.

ответ

112

@Eric С.

ответ

Eric S. превосходен, но я узнал, экспериментальным путем, что это всегда будет вызывать сообщения, регистрируемые на новый уровень отладки для печати - независимо от того, что в уровень журнала установлен.Поэтому, если вы произведете новый уровень 9, если вы вызываете setLevel (50), сообщения нижнего уровня будут ошибочно напечатаны. Чтобы этого не произошло, вам нужна другая строка внутри функции «debugv», чтобы проверить, действительно ли включен уровень ведения журнала.

Fixed пример, который проверяет, включен уровень протоколирования:

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'. 
    if self.isEnabledFor(DEBUG_LEVELV_NUM): 
     self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv 

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

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

+7

Это лучший ответ, потому что он правильно проверяет уровень журнала. –

+3

Это должен быть принятый ответ ... –

+2

Конечно, гораздо более информативный, чем текущий ответ. –

8

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

+3

И вы, вероятно, хотите, чтобы заменить 'logging.getLogger' вернуть подкласс вместо встроенного в классе , –

+0

хорошо, есть ли у вас какие-либо другие рекомендации для 'logging'? – tuergeist

+4

@ S.Lott - На самом деле (по крайней мере, с нынешней версией Python, возможно, это было не так в 2010 году), вы должны использовать ['setLoggerClass (MyClass)'] (https://docs.python.org/ 3/library/logging.html? Highlight = logging # logging.setLoggerClass), а затем вызывать 'getLogger()' как обычно ... – mac

9

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

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

+0

Разве у этого не было бы небольшой производительности по сравнению с подклассом? При таком подходе каждый раз, когда некоторые запрашивают регистратор, они должны будут выполнить вызов setattr. Вы, вероятно, обернете их вместе в пользовательский класс, но тем не менее, что setattr нужно вызывать на каждый созданный журнал, верно? –

+0

@Zbigniew ниже указано, что это не сработало, что, я думаю, связано с тем, что ваш регистратор должен сделать свой вызов '_log', а не' log'. – marqueed

2

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

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).

+0

Я не думаю, что это работает. Вам не нужен «logger» в качестве первого аргумента в log_at_my_log_level? – Paul

+0

Да, я думаю, вы, вероятно, захотите.Этот ответ был адаптирован из кода, который решает несколько другую проблему. – marqueed

53

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

+1. Это правильный ответ. – Macke

+7

+1 тоже. Элегантный подход, и он отлично работал. Важное замечание: *** Вам нужно только сделать это один раз, в одном модуле, и он будет работать для всех модулей ***. Вам даже не нужно импортировать модуль «setup». Поэтому бросьте это в пакет '__init __. Py' и будьте счастливы: D – MestreLion

+4

@ Эрик С. Вы должны взглянуть на этот ответ: http://stackoverflow.com/a/13638084/600110 –

2

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

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.') 

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

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

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

Использование _log() вместо log() необходимо для не вводите дополнительный уровень в стек вызовов. Если используется log(), введение дополнительного стека кадров вызывает несколько атрибутов LogRecord (funcName, lineno, filename, pathname, ...), чтобы указать на функцию отладки вместо фактического вызывающего. Это, скорее всего, не желаемый результат. – rivy

+4

С каких пор вызывать внутренние внутренние методы класса недопустимы? Просто потому, что функция определена вне класса, это не означает, что это внешний метод. – OozeMeister

+3

Этот метод не только излишне изменяет трассировку стека, но также не проверяет правильность ввода правильного уровня. –

0

Как альтернатива добавив дополнительный метод класса 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') 
25

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

Это ИМХО лучший ответ, так как он избегает патчей обезьян. Какие 'get' и' setLoggerClass' точно выполняют и зачем они нужны? –

+1

@MarcoSulla Они задокументированы как часть модуля регистрации Python. Я полагаю, что динамическое подклассирование используется в случае, если кто-то хочет использовать свой собственный логгер при использовании этой библиотеки. Этот MyLogger стал бы подклассом моего класса, объединив два. – CrackerJack9

13

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

Отсоедините ответы от 'Oldest', и вы поймете, что это лучший ответ от всех! –

+0

Спасибо. Я проделал довольно много работы, помогая чему-то подобному вместе, и этот QA был очень полезен, поэтому я попытался что-то добавить. –

1

Советы для создания пользовательского регистратора:

  1. Не используйте _log используйте log (вы не должны check 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 на самом деле з этой низкой Трейс уровня

1

Дополнения к примеру Mad Физики получить имя файла и номер строки правильно:

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

в случае, если кто хочет автоматизированный способ, чтобы добавить новый уровень ведения журнала в модуль регистрации (или его копию) динамически, я создал эту функцию, расширяя @ ответ, 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() 
    exec('custom_log_module.Logger.'+eval('log_call')+' = cust_log', None, locals()) 
    return custom_log_module 
+1

Eval внутри exec. Вау. –

+1

..... не знаю, что заставило меня сделать это .... после стольких месяцев я бы с радостью заменил это заявление на 'setattr' вместо этого ... –

0

Я в замешательстве; с питоном 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:root:y1

TRACE:root:y2

Смежные вопросы