로그

../_images/35254379756_c9fe23f843_k_d.jpg

logging 모듈은 2.3 버전 이래로 파이썬의 표준 라이브러리였습니다. 이에 관해서는 PEP 282 을 보시면 간략히 써져 있습니다. 그 문서는 읽기 어렵기로 악명이 높지만, 그래도 basic logging tutorial 은 그나마 낫습니다.

As an alternative, loguru provides an approach for logging, nearly as simple as using a simple print statement.

로그를 남기는데에는 2가지 목적이 있습니다:

  • 진단용 로그 는 어플리케이션의 동작과 관련된 이벤트를 기록합니다. 예를 들어 사용자가 오류 보고서를 남기면, 그 로그를 해당 에러와 관련된 상황을 확인하는데 쓸 수 있습니다.
  • 감사용 로그 는 비지니스 분석에 필요한 이벤트를 기록합니다. 사용자가 무슨 동작을 했는지 알아낼 수 있으며, 다른 사용자와는 무슨 동작을 했는지도 상세하게 알 수 있습니다. 이를 통해 보고서를 작성하거나 업무적으로 최적화를 할 수 있습니다.

... Print는?

print 가 logging보다 좋은 경우는 커맨드라인 어플리케이션에서 help 구문을 화면에 보여줄 때 뿐입니다. logging이 print 보다 좋은 이유입니다:

  • log record 는 로그 남기는 이벤트가 발생할 때마다 만들어지는데, 여기에는 로그 남기는 이벤트의 파일명과 경로, 함수, 몇 행에서 문제가 발생했는지 등의 정보가 들어있어 문제를 확인하기에 편리합니다.
  • 내장된 모듈에서 발생한 이벤트들도 로그가 남는데, 이 로그들은 루트 로그 기록기를 통하여 어플리케이션의 로그 스트림으로 보낼 수 있습니다. 필터링해서 걸러내지만 않는다면 말입니다.
  • logging.Logger.setLevel() 메소드를 쓰면 로그를 선택적으로 남길 수 있습니다. logging.Logger.disabled 속성을 True 로 설정하면 로그를 끌 수도 있습니다.

라이브러리에서의 로그 남기기

라이브러리에 로그 설정 하려면 로그 남기기 튜토리얼 을 보시면 됩니다. 로그를 남기는 이벤트가 발생하면 그게 무슨 일인지 알아내야 하는 건 라이브러리가 아니라 사용자 입니다. 따라서 반복적으로 경보를 보내야합니다.

주석

It is strongly advised that you do not add any handlers other than NullHandler to your library’s loggers.

라이브러리에서 로그 기록기를 인스턴스화 하는 유일한 방법은 __name__ 전역 변수를 사용해서 만드는 방법 뿐입니다. logging 모듈은 . 을 사용해서 로그 기록기의 계층 구조를 만들기 때문에 __name__ 을 사용해야 충돌을 막을 수 있습니다.

Here is an example of the best practice from the requests source -- place this in your __init__.py:

import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

어플리케이션에서의 로그 남기기

The twelve factor app, an authoritative reference for good practice in application development, contains a section on logging best practice. It emphatically advocates for treating log events as an event stream, and for sending that event stream to standard output to be handled by the application environment.

로그를 설정하는데에는 적어도 3가지 방식이 있습니다:

  • INI 포맷의 파일을 사용하는 방법:
    • Pro: possible to update configuration while running, using the function logging.config.listen() to listen on a socket.
    • Con: less control (e.g. custom subclassed filters or loggers) than possible when configuring a logger in code.
  • 딕셔너리나 JSON 포맷 파일을 사용하는 방법:
    • 이렇게 하자: 어플리케이션 실행 중에도 업데이트를 할 수 있을 뿐만 아니라, 파이썬 2.6부터는 표준 라이브러리에서 json 모듈을 사용하여 파일에서 설정을 불러올 수도 있다.
    • 이렇게 하지 마세요: 코드에 로그를 설정할 때 할 수 있는 설정도 하지 않는다
  • 코드를 사용하는 방법:
    • 이렇게 하자: 모든 설정을 완벽하게 한다.
    • Con: modifications require a change to the source code.

INI 파일로 설정하는 예시

Let us say that the file is named logging_config.ini. More details for the file format are in the logging configuration section of the logging tutorial.

[loggers]
keys=root

[handlers]
keys=stream_handler

[formatters]
keys=formatter

[logger_root]
level=DEBUG
handlers=stream_handler

[handler_stream_handler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s

그런 다음 코드에 logging.config.fileConfig() 를 쓰세요.

import logging
from logging.config import fileConfig

fileConfig('logging_config.ini')
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

딕셔너리로 설정하는 예시

파이썬 2.7부터는 딕셔너리를 사용해서 상세한 설정을 할 수 있습니다. PEP 391 을 보시면 설정 딕셔너리에 반드시 넣어야 하는 요소와 그렇지 않은 요소를 확인할 수 있습니다.

import logging
from logging.config import dictConfig

logging_config = dict(
    version = 1,
    formatters = {
        'f': {'format':
              '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
        },
    handlers = {
        'h': {'class': 'logging.StreamHandler',
              'formatter': 'f',
              'level': logging.DEBUG}
        },
    root = {
        'handlers': ['h'],
        'level': logging.DEBUG,
        },
)

dictConfig(logging_config)

logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

코드에 바로 설정하는 예시

import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.debug('often makes a very good meal of %s', 'visiting tourists')