[python] 새 형식 문자열로 변수 데이터 로깅

파이썬 2.7.3에 로깅 기능을 사용합니다. 이 Python 버전에 대한 문서는 다음과 같습니다.

로깅 패키지는 str.format () 및 string.Template과 같은 최신 형식화 옵션보다 이전입니다. 이러한 최신 서식 옵션이 지원됩니다 …

중괄호가있는 ‘새’형식을 좋아합니다. 그래서 나는 다음과 같은 것을 시도하고 있습니다.

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

그리고 오류가 발생합니다.

TypeError : 문자열 형식화 중에 일부 인수가 변환되지 않았습니다.

내가 여기서 무엇을 그리워?

PS 나는 사용하고 싶지 않다

log.debug("format this message {0}".format(1))

이 경우 메시지는 로거 레벨에 관계없이 항상 형식화되기 때문입니다.



답변

편집 :StyleAdapter답변 과 달리 @Dunes의 답변에서 접근 방식을 살펴보십시오 . 로거의 메서드 (debug (), info (), error () 등)를 호출하는 동안 상용구없이 대체 형식화 스타일을 사용할 수 있습니다.


문서에서 — 대체 서식 스타일 사용 :

로깅 호출 (logger.debug (), logger.info () 등)은 실제 로깅 메시지 자체에 대한 위치 매개 변수 만 취하며, 키워드 매개 변수는 실제 로깅 호출을 처리하는 방법에 대한 옵션을 결정하는 데만 사용됩니다 (예 : exc_info 키워드 매개 변수). 추적 정보를 기록해야 함을 나타내거나 로그에 추가 할 추가 컨텍스트 정보를 나타내는 추가 키워드 매개 변수). 따라서 내부적으로 로깅 패키지는 %-포맷을 사용하여 형식 문자열과 변수 인수를 병합하기 때문에 str.format () 또는 string.Template 구문을 사용하여 로깅 호출을 직접 할 수 없습니다. 기존 코드에있는 모든 로깅 호출이 % 형식 문자열을 사용하기 때문에 이전 버전과의 호환성을 유지하면서이를 변경할 수 없습니다.

과:

그러나 {}-및 $-형식을 사용하여 개별 로그 메시지를 구성 할 수있는 방법이 있습니다. 메시지의 경우 임의의 개체를 메시지 형식 문자열로 사용할 수 있으며 로깅 패키지는 해당 개체에서 str ()을 호출하여 실제 형식 문자열을 가져올 수 있습니다.

이것을 wherever모듈에 복사-붙여 넣기 :

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

그때:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

참고 : 실제 포맷은 필요할 때까지 지연됩니다. 예를 들어 DEBUG 메시지가 기록되지 않으면 포맷이 전혀 수행되지 않습니다.


답변

Dunes의 답변에 언급 된 키워드 문제가없는 또 다른 옵션이 있습니다. {0}키워드 ( {foo}) 인수가 아닌 위치 ( ) 인수 만 처리 할 수 ​​있습니다 . 또한 형식을 지정하기 위해 두 번의 호출이 필요하지 않습니다 (밑줄 사용). 하위 클래스의 ick-factor가 있습니다 str.

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

다음과 같이 사용합니다.

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

물론 # optional어댑터를 통한 모든 메시지가 새 스타일 형식을 사용하도록 강제하기 위해 표시된 확인을 제거 할 수 있습니다 .


몇 년 후이 답변을 읽는 모든 사람을위한 참고 사항 : Python 3.2 부터는 객체 와 함께 스타일 매개 변수사용할있습니다Formatter .

로깅 (3.2부터)은 이러한 두 가지 추가 서식 스타일에 대한 향상된 지원을 제공합니다. Formatter 클래스는라는 추가 선택적 키워드 매개 변수를 사용하도록 향상되었습니다 style. 기본값은 '%'이지만 다른 가능한 값은 다른 두 서식 스타일에 해당하는 '{''$'입니다. 이전 버전과의 호환성은 기본적으로 유지되지만 (예상대로) 명시 적으로 스타일 매개 변수를 지정하면 str.format()또는에서
작동하는 형식 문자열을 지정할 수 string.Template있습니다.

문서는 예제를 제공합니다.
logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

이 경우에도 logger새 형식으로를 호출 할 수 없습니다 . 즉, 다음은 여전히 ​​작동하지 않습니다.

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either


답변

이것은 로깅이 printf 스타일 형식화만을 사용한다는 것을 알았을 때 문제에 대한 나의 해결책이었습니다. 로깅 호출을 동일하게 유지할 수 있습니다 log.info(__("val is {}", "x")). 코딩에 필요한 변경 사항은 로거를 StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (),
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key]
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

사용법은 다음과 같습니다.

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

중괄호 대체에 사용되는 핵심 단어를 포함하는 경우이 구현에 문제가 있다는 지적이의 가치 level, msg, args, exc_info, extra또는 stack_info. 의 log메서드에서 사용하는 인수 이름 입니다 Logger. 이러한 이름 중 하나가 필요한 경우 이러한 이름 process을 제외하도록 수정 하거나 호출 log_kwargs에서 제거 _log하십시오. 추가로,이 구현은 Logger (예 :)를 의미하는 맞춤법이 틀린 키워드를 조용히 무시합니다 ectra.


답변

더 쉬운 해결책은 우수한 모듈 을 사용하는 것입니다.logbook

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

또는 더 완전한 :

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1


답변

다른 답변에서 언급했듯이 Python 3.2에 도입 된 중괄호 스타일 형식 은 실제 로그 메시지가 아닌 형식 문자열에만 사용됩니다.

실제 로그 메시지에서 중괄호 형식의 형식을 사용하려면 로거 코드를 약간 원숭이 패치 할 수 있습니다.

다음은 logging모듈을 패치하여 get_logger처리하는 모든 로그 레코드에 대해 새로운 스타일 형식을 사용하는 로거를 반환 하는 함수 를 만듭니다 .

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

용법:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

메모:

  • 일반 로깅 방법 (바로 교체와 완벽하게 호환 logging.getLoggerget_logger)
  • get_logger함수에 의해 생성 된 특정 로거에만 영향을 미칩니다 (타사 패키지를 손상시키지 않음).
  • 일반 logging.getLogger()호출 에서 로거에 다시 액세스하는 경우 새 스타일 형식이 계속 적용됩니다.
  • kwargs로는 (차종이 불가능와 충돌에 내장에서 지원되지 않습니다 exc_info, stack_info, stacklevelextra)를.
  • 성능 적중은 최소화되어야합니다 (각 로그 메시지에 대해 단일 함수 포인터를 다시 작성).
  • 메시지 형식화는 출력 될 때까지 지연됩니다 (또는 로그 메시지가 필터링 된 경우에는 아예 아님).
  • Args는 logging.LogRecord평소와 같이 개체 에 저장됩니다 (사용자 지정 로그 처리기를 사용하는 경우에 유용함).
  • logging모듈 소스 코드를 살펴보면 Python 2.6 str.format이 도입 되었을 때 다시 작동해야하는 것처럼 보입니다 (하지만 3.5 이상에서만 테스트했습니다).


답변

logging.setLogRecordFactoryPython 3.2 이상에서 사용 해보세요 .

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)


답변

다음 과 같이 문제를 처리하는 ColorFormatter 라는 사용자 지정 Formatter를 만들었습니다 .

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

이것은 다양한 라이브러리와의 호환성을 유지합니다. 단점은 잠재적으로 문자열 형식을 두 번 시도하기 때문에 성능이 떨어질 수 있다는 것입니다.