Python의 로깅 기능에 사용자 지정 로그 수준을 추가하는 방법


116

나는 그것이 debug()충분 하지 않다고 생각하기 때문에 내 응용 프로그램에 대해 로그 레벨 TRACE (5)를 갖고 싶습니다 . 또한 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")

도움을 주셔서 미리 감사드립니다.

편집 (2016 년 12 월 8 일) : Eric S의 아주 좋은 제안을 기반으로 한 탁월한 솔루션 인 IMHO 인 pfa에 대한 수락 된 답변을 변경했습니다 .

답변:


171

@ 에릭 S.

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

Python 2.7 용 class Loggerin 코드를 살펴보면 logging.__init__.py모든 표준 로그 함수 (.critical, .debug 등)가 수행하는 작업입니다.

평판이 부족해서 다른 사람의 답변에 대한 답글을 게시 할 수없는 것 같습니다. Eric이이 사실을 발견하면 게시물을 업데이트하기를 바랍니다. =)


7
로그 수준을 올바르게 확인하기 때문에 이것이 더 나은 대답입니다.
Colonel Panic

2
현재 답변보다 확실히 훨씬 더 유익합니다.
Mad Physicist

4
@pfa logging.DEBUG_LEVEL_NUM = 9코드에서 로거를 가져 오는 모든 곳에서 해당 디버그 수준에 액세스 할 수 있도록 추가하는 것은 어떻습니까?
edgarstack

4
확실히 대신 DEBUG_LEVEL_NUM = 9정의해야합니다 logging.DEBUG_LEVEL_NUM = 9. 이렇게하면 log_instance.setLevel(logging.DEBUG_LEVEL_NUM)right know logging.DEBUG또는logging.INFO
maQ

이 답변은 매우 도움이되었습니다. pfa와 EricS에게 감사합니다. 나는 완전성에 대한 두 개 더 문을 포함 할 것을 제안하고 싶습니다 logging.DEBUGV = DEBUG_LEVELV_NUM그리고 logging.__all__ += ['DEBUGV'] 두 번째는 정말 중요하지 않습니다하지만 당신은 동적 로깅 수준을 조정하는 코드를 가지고 당신이 뭔가를 할 수하려는 경우 먼저 필요하다 if verbose: logger.setLevel(logging.DEBUGV)`
키스 Hanlan dec.

63

나는 "람다를 보지 않기"답을 취했고 log_at_my_log_level이 추가되는 위치를 수정해야했습니다. 나도 Paul이 "이것이 작동하지 않는다고 생각합니다. log_at_my_log_level의 첫 번째 인수로 logger가 필요하지 않습니까?"라는 문제를 보았습니다. 이것은 나를 위해 일했습니다.

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

7
+1도. 우아한 접근 방식이며 완벽하게 작동했습니다. 중요 사항 : 단일 모듈에서이 작업을 한 번만 수행하면 모든 모듈에서 작동합니다 . "설정"모듈을 가져올 필요도 없습니다. 그래서 패키지의이 토스 __init__.py와 행복 : D
MestreLion

4
@Eric S.이 대답을 살펴해야 : stackoverflow.com/a/13638084/600110를
샘 Mussmann에게

1
@SamMussmann에 동의합니다. 나는 이것이 가장 많이 득표 한 답변이기 때문에 그 답변을 놓쳤습니다.
Colonel Panic

@Eric S. 왜 *없이 args가 필요합니까? 그렇게하면 얻을 수 TypeError: not all arguments converted during string formatting있지만 *와 잘 작동합니다. (Python 3.4.3). 파이썬 버전 문제입니까, 아니면 내가 놓친 것입니까?
Peter

이 대답은 저에게 효과가 없습니다. 는 'logging.debugv'을 수행하려고하면 오류가 제공AttributeError: module 'logging' has no attribute 'debugv'
알렉스

51

기존의 모든 답변과 많은 사용 경험을 결합하여 새로운 레벨을 완벽하게 원활하게 사용하기 위해 수행해야 할 모든 일의 목록을 생각해 냈다고 생각합니다. 아래 단계에서는 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)

으로 답변을 정렬하면이 답변 Oldest이 모두 가장 좋은 답변임을 알게 될 것입니다!
Serge Stroobandt

감사. 나는 이와 같은 것을 함께 결합하는 작업을 상당히 많이했고이 QA는 매우 도움이 되었기 때문에 무언가를 추가하려고했습니다.
Mad Physicist

1
@PeterDolan. 이 문제가 있으면 알려주세요. 내 개인 도구 상자에는 충돌하는 수준 정의를 처리하는 방법을 구성 할 수있는 확장 버전이 있습니다. TRACE 레벨을 추가하고 싶기 때문에 한 번 떠 올랐고 스핑크스의 구성 요소 중 하나도 마찬가지입니다.
Mad Physicist

1
앞에 별표 (*)의 부족이되어 args에서 logForLevel의도적 구현 / 필요?
Chris L. Barnes

1
@ 튀니지. 의도하지 않은 것입니다. 잡아 주셔서 감사합니다.
Mad Physicist

40

이 질문은 다소 오래되었지만 동일한 주제를 다루었 고 이미 언급 한 것과 유사한 방법을 찾았는데, 이는 나에게 조금 더 깔끔해 보입니다. 이것은 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)

1
이것은 원숭이 패치를 피하기 때문에 IMHO가 가장 좋은 대답입니다. 무엇을 get하고 setLoggerClass그들이 필요한 이유를 정확히 수행하고?
Marco Sulla 2015 년

3
@MarcoSulla 그들은 Python의 로깅 모듈의 일부로 문서화되어 있습니다. 동적 서브 클래 싱은이 라이브러리를 사용하는 동안 누군가가 자신의 llogger를 원할 경우에 사용됩니다. 이 MyLogger는 두 가지를 결합하여 내 클래스의 하위 클래스가됩니다.
CrackerJack9 2016-10-24

이것은 기본 로깅 라이브러리에 레벨을 추가할지 여부 에 대해이 토론 에서 제시된 솔루션과 매우 유사합니다 TRACE. +1
IMP1

18

내부 방법 ( self._log) 을 사용하는 나쁜 관행을 시작한 사람은 누구 이며 각 답변이 그에 기반한 이유는 무엇입니까?! Pythonic 솔루션은 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')

18
호출 스택에 추가 수준을 도입하지 않으려면 log () 대신 _log ()를 사용해야합니다. log ()가 사용되는 경우 추가 스택 프레임의 도입으로 인해 여러 LogRecord 속성 (funcName, lineno, 파일 이름, 경로 이름, ...)이 실제 호출자가 아닌 디버그 함수를 가리 킵니다. 이것은 원하는 결과가 아닐 수 있습니다.
rivy

5
언제부터 클래스의 자체 내부 메서드를 호출 할 수 없습니까? 함수가 클래스 외부에서 정의되었다고해서 외부 메서드라는 의미는 아닙니다.
OozeMeister 2015 년

3
이 방법은 스택 추적을 불필요하게 변경할뿐만 아니라 올바른 수준이 기록되고 있는지 확인하지 않습니다.
Mad Physicist

@schlamar가 말하는 것이 옳다고 생각하지만 반대 이유는 같은 수의 표를 얻었습니다. 그래서 무엇을 사용합니까?
Sumit Murari 2017

1
메서드가 내부 메서드를 사용하지 않는 이유는 무엇입니까?
Gringo Suave

9

log () 함수를 전달하는 로거 객체에 대한 새 속성을 만드는 것이 더 쉽습니다. 로거 모듈이 바로 이런 이유로 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')

예상대로 작동합니다.


이것은 서브 클래 싱에 비해 약간의 성능 저하가 있지 않을까요? 이 접근 방식을 사용하면 일부 사용자가 로거를 요청할 때마다 setattr 호출을 수행해야합니다. 아마도 이것들을 사용자 정의 클래스로 함께 래핑 하겠지만 그럼에도 불구하고 생성 된 모든 로거에서 해당 setattr을 호출해야합니다.
Matthew Lund

@Zbigniew 아래이 내가 로거의 요구에의 호출을 할 수 있기 때문이라고 생각 일,하지 않았다 표시 _log하지 log.
Marqueed

9

우리는 이미 많은 정답을 가지고 있지만, 내 생각에는 다음이 더 비단뱀 적이라고 생각합니다.

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속성 추가에서 경고를 표시하지 않도록 추가 하는 것이 좋습니다 .


1
멋져 보이지만 마지막 줄은 혼란 스럽습니다. 안 logging.trace = partial(logging.log, logging.TRACE) # type: ignore그래?
Sergey Nudnov

@SergeyNudnov 지적 해 주셔서 감사합니다. 내 쪽의 실수 였고, 방금 내 코드에서 복사하여 청소를 엉망으로 만든 것 같습니다.
DerWeh

8

Logger클래스 를 하위 클래스로 만들고 trace기본적으로 Logger.log보다 낮은 수준 으로 호출 하는 메서드를 추가 해야한다고 생각합니다 DEBUG. 나는 이것을 시도하지 않았지만 이것은 문서가 나타내는 것 입니다.


3
그리고 logging.getLogger기본 제공 클래스 대신 하위 클래스를 반환 하도록 교체하고 싶을 것입니다 .
S.Lott

4
@ S.Lott-실제로 (적어도 현재 버전의 Python에서는 2010 년에는 그렇지 않았을 수도 있습니다) 사용 setLoggerClass(MyClass)하고 getLogger()정상적으로 호출 해야합니다 ...
mac

IMO, 이것은 최고의 (그리고 대부분의 Pythonic) 대답이며, 여러 개의 +1을 줄 수 있었다면 그럴 것입니다. 실행은 간단하지만 샘플 코드는 좋았을 것입니다. :-D
Doug R.

@DougR. 감사하지만 내가 말했듯이 나는 그것을 시도하지 않았습니다. :)
Noufal Ibrahim

6

사용자 정의 로거 생성을위한 팁 :

  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는 자체


3

이것은 나를 위해 일했습니다.

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 문제는 @marqueed가 지적한대로 logger._log로 해결됩니다. 람다를 사용하는 것이 조금 더 깔끔해 보이지만 단점은 키워드 인수를 사용할 수 없다는 것입니다. 나는 그것을 직접 사용하지 않았으므로 큰 문제가 아닙니다.

  참고 설정 : 여름에 학교가 나갑니다! 친구
  치명적 설정 : 파일을 찾을 수 없습니다.

2

내 경험상, 이것이 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

독립형 로거 클래스로 작업 해 본 적이 없지만 기본 아이디어는 동일하다고 생각합니다 (_log 사용).


나는 이것이 효과가 있다고 생각하지 않는다. logger첫 번째 인수로 필요하지 log_at_my_log_level않습니까?
Paul

네, 아마 그럴 것 같아요. 이 답변은 약간 다른 문제를 해결하는 코드에서 채택되었습니다.
2011

2

Mad Physicists 예제에 추가하여 올바른 파일 이름과 줄 번호를 얻으십시오.

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

1

고정 된 답변을 기반으로 새로운 로깅 수준을 자동으로 생성하는 작은 방법을 작성했습니다.

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,
}

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

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


1
이것은 logger.trace('hi')내가 주요 목표라고 믿는 것을 당신이 할 수있게하지 않습니다
Ultimation

-3

누군가가 로깅 모듈 (또는 그 사본)에 동적으로 새 로깅 수준을 추가하는 자동화 된 방법을 원하는 경우이 함수를 만들어 @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

1
임원 내부 평가. 와.
Mad Physicist

2
..... 내가이 일을하게 만든 이유를 모르겠습니다 .... 너무 많은 달이 지나면이 성명을 setattr대신 행복하게 교환 할 것입니다 ...
Vasilis Lemonidis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.