Python 로그 형식 문자열에 사용자 정의 필드를 어떻게 추가합니까?


91

내 현재 형식 문자열은 다음과 같습니다.

formatter = logging.Formatter('%(asctime)s : %(message)s')

app_name이 포맷터를 포함하는 각 스크립트에서 다른 값을 가질 새 필드를 추가하고 싶습니다 .

import logging
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.addHandler(syslog)

그러나 해당 app_name값을 로거에 전달 하여 형식 문자열로 보간 하는 방법을 모르겠습니다 . 매번 전달하여 로그 메시지에 분명히 나타날 수 있지만 이것은 지저분합니다.

난 노력 했어:

logging.info('Log message', app_name='myapp')
logging.info('Log message', {'app_name', 'myapp'})
logging.info('Log message', 'myapp')

하지만 작동하지 않습니다.


정말 모든 log호출에 이것을 전달 하시겠습니까? 그렇다면 "이 기능은 LogRecord에 자신의 값을 삽입하는 데 사용할 수 있습니다."라고 표시된 문서 를 참조하십시오. 그러나 이것은 사용 logger = logging.getLogger('myapp')하고 logger.info호출에 구워지는 주요 사례처럼 보입니다 .
abarnert 2013

파이썬 로깅은 이미 그렇게 할 수 있습니다. logger각 앱에서 다른 개체 를 사용하는 경우 다음 logger과 같이 인스턴스화하여 각 개체가 다른 이름을 사용하도록 할 수 있습니다 logger = logging.getLogger(myAppName). 참고 __name__각 응용 프로그램은 자신의 파이썬 모듈 인 경우 그래서, 즉 잘 작동, 파이썬 모듈 이름입니다.
Florian Castellane

답변:


131

LoggerAdapter를 사용하면 모든 로깅 호출에 추가 정보를 전달할 필요가 없습니다.

import logging
extra = {'app_name':'Super App'}

logger = logging.getLogger(__name__)
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger = logging.LoggerAdapter(logger, extra)
logger.info('The sky is so blue')

로그 (같은 것)

2013-07-09 17:39:33,596 Super App : The sky is so blue

필터 를 사용하여 컨텍스트 정보를 추가 할 수도 있습니다.

import logging

class AppFilter(logging.Filter):
    def filter(self, record):
        record.app_name = 'Super App'
        return True

logger = logging.getLogger(__name__)
logger.addFilter(AppFilter())
syslog = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(app_name)s : %(message)s')
syslog.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(syslog)

logger.info('The sky is so blue')

유사한 로그 레코드를 생성합니다.


3
config.ini파일 에서 어떻게 지정할 수 있습니까? 현재 호스트 이름을 추가하고 싶습니다 socket.gethostname().
Laurent LAPORTE

이 샘플은 나를 위해 작동하지 않습니다. import uuid uniqueId = str(uuid.uuid4()) extra = {"u_id" : uniqueId} RotatingHandler = RotatingFileHandler(LOG_FILENAME,encoding='utf-8',maxBytes=maxSize, backupCount=batchSize) logger.basicConfig(handlers=[RotatingHandler],level=logLevel.upper(),format='%(levelname)s %(u_id)s %(funcName)s %(asctime)s %(message)s ',datefmt='%m/%d/%Y %I:%M:%S %p') logger = logger.LoggerAdapter(logger=logger, extra=extra)
Hayat

"levelname"과 동일한 "level"필드를 추가 할 수 있습니까? 참조 : Python 로그 메시지에서 "levelname"을 "level"로 이름을 바꾸려면 어떻게해야합니까?
Martin Thoma

2
추가 정보 문자열을 전달할 수 있습니까? 다음과 같습니다. "직원 ID 1029382에 오류가 발생했습니다."사전을 만들지 않았습니다.
shreesh katti

50

그렇게하려면 extra에 매개 변수로 dict를 전달해야합니다.

logging.info('Log message', extra={'app_name': 'myapp'})

증명:

>>> import logging
>>> logging.basicConfig(format="%(foo)s - %(message)s")
>>> logging.warning('test', extra={'foo': 'bar'})
bar - test 

또한 참고로 dict를 전달하지 않고 메시지를 기록하려고하면 실패합니다.

>>> logging.warning('test')
Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 846, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 723, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 467, in format
    s = self._fmt % record.__dict__
KeyError: 'foo'
Logged from file <stdin>, line 1

이것도 작동 logging.info()합니까? 내가 마지막으로 시도했을 때 실패했습니다. : /
Prakhar 모한 스리 바스타

2
나는 @ mr2ert 대답을 좋아합니다. logging.Formatter클래스 를 확장하여 추가 필드에 기본값을 제공 할 수 있습니다 . class CustomFormatter (logging.Formatter) : def format (self, record) : if not hasattr (record, 'foo') : record.foo = 'default_foo'return super (CustomFormatter, self.format (record) h = loggin.StreamHandler () h.setFormatter (CustomFormatter ( '% (foo) s % (message) s') logger = logging.getLogger ( 'bar') logger.addHandler ( h) logger.error ( 'hey!', extra = { 'foo': 'FOO'}) logger.error ( 'hey!')
loutre

이 방법은 더 빠르지 만 잊기 쉽고 오류가 발생하기 쉬운 각 로그 메시지에 추가 행을 추가해야합니다. super () 호출을 대체하는 것은 unutbu의 대답보다 더 지저분합니다.
pevogam

@Prakhar Mohan Srivastava 예 logging.info ()에서도 잘 작동합니다. 어떤 오류 메시지가 표시됩니까?
shreesh katti

추가 정보 문자열을 전달할 수 있습니까? 이런 식으로 뭔가가 : 어떤 사전을 생성하고 키를 전달하지 않고 "오류 직원 ID 1029382에 대한 발생했습니다"
shreesh 카티

23

Python3

Python3.2부터는 이제 LogRecordFactory를 사용할 수 있습니다.

>>> import logging
>>> logging.basicConfig(format="%(custom_attribute)s - %(message)s")
>>> old_factory = logging.getLogRecordFactory()
>>> def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.custom_attribute = "my-attr"
        return record

>>> logging.setLogRecordFactory(record_factory)
>>> logging.info("hello")
my-attr - hello

물론 record_factory모든 호출 가능으로 사용자 정의 할 custom_attribute수 있으며 팩토리 호출 가능에 대한 참조를 유지하면 의 값을 업데이트 할 수 있습니다.

어댑터 / 필터를 사용하는 것보다 더 나은 이유는 무엇입니까?

  • 로거를 응용 프로그램에 전달할 필요가 없습니다.
  • 실제로 자체 로거를 사용하는 타사 라이브러리에서 작동합니다 (를 호출하여 logger = logging.getLogger(..)) 이제 동일한 로그 형식을 갖게됩니다. (동일한 로거 개체를 사용해야하는 필터 / 어댑터에는 해당되지 않습니다.)
  • 여러 공장을 쌓거나 연결할 수 있습니다.

Python 2.7의 대안이 있습니까?
karolch

동일한 이점이 아니라 2.7을 사용하면 어댑터 또는 필터를 사용해야합니다.
Ahmad

5
이것은 오늘날의 python3 베스트 답변입니다
Stéphane

docs.python.org/3/howto/logging-cookbook.html 에 따르면 :이 패턴을 사용하면 서로 다른 라이브러리가 팩토리를 함께 연결할 수 있으며, 서로의 속성을 덮어 쓰거나 의도 치 않게 표준으로 제공된 속성을 덮어 쓰지 않는 한 놀랄 일이 아닙니다. 그러나 체인의 각 링크는 모든 로깅 작업에 런타임 오버 헤드를 추가하고 필터를 사용하여 원하는 결과를 제공하지 않는 경우에만이 기술을 사용해야합니다.
steve0hh

1
@ steve0hh 원하는 주요 결과 중 하나는 이러한 방식으로 만 달성 할 수있는 다양한 라이브러리 / 모듈에 걸쳐 컨텍스트 정보를 기록하는 기능입니다. 대부분의 상황에서 라이브러리는 로거 구성을 건드리지 않아야하며 상위 애플리케이션의 책임입니다.
Ahmad

9

또 다른 방법은 사용자 지정 LoggerAdapter를 만드는 것입니다. 이는 형식을 변경할 수 없거나 형식이 고유 키를 전송하지 않는 코드 (예 : app_name ) 와 공유되는 경우 특히 유용합니다 .

class LoggerAdapter(logging.LoggerAdapter):
    def __init__(self, logger, prefix):
        super(LoggerAdapter, self).__init__(logger, {})
        self.prefix = prefix

    def process(self, msg, kwargs):
        return '[%s] %s' % (self.prefix, msg), kwargs

그리고 코드에서 평소와 같이 로거를 만들고 초기화합니다.

    logger = logging.getLogger(__name__)
    # Add any custom handlers, formatters for this logger
    myHandler = logging.StreamHandler()
    myFormatter = logging.Formatter('%(asctime)s %(message)s')
    myHandler.setFormatter(myFormatter)
    logger.addHandler(myHandler)
    logger.setLevel(logging.INFO)

마지막으로 래퍼 어댑터를 만들어 필요에 따라 접두사를 추가합니다.

    logger = LoggerAdapter(logger, 'myapp')
    logger.info('The world bores you when you are cool.')

출력은 다음과 같습니다.

2013-07-09 17:39:33,596 [myapp] The world bores you when you are cool.

1

이 질문을 직접 구현 한 후 발견했습니다. 누군가에게 도움이되기를 바랍니다. 아래 코드 claim_id에서 로거 형식으로 호출 된 추가 키를 유도하고 있습니다. claim_id환경에 키 가있을 때마다 claim_id를 기록 합니다. 제 사용 사례에서는 AWS Lambda 함수에 대해이 정보를 기록해야했습니다.

import logging
import os

LOG_FORMAT = '%(asctime)s %(name)s %(levelname)s %(funcName)s %(lineno)s ClaimID: %(claim_id)s: %(message)s'


class AppLogger(logging.Logger):

    # Override all levels similarly - only info overriden here

    def info(self, msg, *args, **kwargs):
        return super(AppLogger, self).info(msg, extra={"claim_id": os.getenv("claim_id", "")})


def get_logger(name):
    """ This function sets log level and log format and then returns the instance of logger"""
    logging.setLoggerClass(AppLogger)
    logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    return logger


LOGGER = get_logger(__name__)

LOGGER.info("Hey")
os.environ["claim_id"] = "12334"
LOGGER.info("Hey")

요점 : https://gist.github.com/ramanujam/306f2e4e1506f302504fb67abef50652


0

mr2ert의 답변을 사용하여 다음과 같은 편안한 솔루션을 찾았습니다. (권장되지는 않지만) 기본 제공 로깅 메서드를 재정 의하여 사용자 지정 인수를 수락 extra하고 메서드 내부에 사전을 만듭니다 .

import logging

class CustomLogger(logging.Logger):

   def debug(self, msg, foo, *args, **kwargs):
       extra = {'foo': foo}

       if self.isEnabledFor(logging.DEBUG):
            self._log(logging.DEBUG, msg, args, extra=extra, **kwargs)

   *repeat for info, warning, etc*

logger = CustomLogger('CustomLogger', logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(foo)s] %(message)s') 
handler = logging.StreamHandler()
handler.setFormatter(formatter) 
logger.addHandler(handler)

logger.debug('test', 'bar')

산출:

2019-03-02 20:06:51,998 [bar] test

이것은 참조를위한 내장 함수입니다.

def debug(self, msg, *args, **kwargs):
    """
    Log 'msg % args' with severity 'DEBUG'.

    To pass exception information, use the keyword argument exc_info with
    a true value, e.g.

    logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
    """
    if self.isEnabledFor(DEBUG):
        self._log(DEBUG, msg, args, **kwargs)

0

수입 로깅;

LogFilter (logging.Filter) 클래스 :

def __init__(self, code):
    self.code = code

def filter(self, record):
    record.app_code = self.code
    return True

logging.basicConfig (format = '[% (asctime) s : % (levelname) s] :: [% (module) s-> % (name) s]-APP_CODE : % (app_code) s-MSG : % (message )에스');

로거 클래스 :

def __init__(self, className):
    self.logger = logging.getLogger(className)
    self.logger.setLevel(logging.ERROR)

@staticmethod
def getLogger(className):
    return Logger(className)

def logMessage(self, level, code, msg):
    self.logger.addFilter(LogFilter(code))

    if level == 'WARN':        
        self.logger.warning(msg)
    elif level == 'ERROR':
        self.logger.error(msg)
    else:
        self.logger.info(msg)

테스트 클래스 : logger = Logger.getLogger ( 'Test')

if __name__=='__main__':
    logger.logMessage('ERROR','123','This is an error')

이 구현은 매우 비효율적입니다.
blakev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.