로그

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

로그를 남기는데에는 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__ 을 사용해야 충돌을 막을 수 있습니다.

requests의 소스 에서 가져온 훌륭한 예시를 보여드리겠습니다. 이걸 당신의 __init__.py 에 두시면 됩니다.

# Set default logging handler to avoid "No handler found" warnings.
import logging
try:  # Python 2.7+
    from logging import NullHandler
except ImportError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

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

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

멋진 앱을 만들기 위한 12가지 는 어플리케이션 개발을 위한 좋은 습관을 들이기에 참고할만 합니다. 로그를 남기는 방법 에 관한 섹션도 있는데, 여기서는 로그 이벤트를 이벤트 스트림으로 취급하여 어플리케이션 환경에서 조작할 수 있는 표준적인 산출물로 만들 것을 강력하게 추천합니다.

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

  • INI 포맷의 파일을 사용하는 방법:
    • 이렇게 하자: 소켓에 함수 logging.config.listen() 를 사용해서 어플리케이션이 실행 중일때도 설정 변경이 반영될 수 있도록 한다.

    • 이렇게 하지 마세요: 코드에 로그를 설정할 때 (예를 들면 직접 만든 서브클래스나 로그에) 할 수 있는 설정도 하지 않는다.

  • 딕셔너리나 JSON 포맷 파일을 사용하는 방법:
    • 이렇게 하자: 어플리케이션 실행 중에도 업데이트를 할 수 있을 뿐만 아니라, 파이썬 2.6부터는 표준 라이브러리에서 json 모듈을 사용하여 파일에서 설정을 불러올 수도 있다.

    • 이렇게 하지 마세요: 코드에 로그를 설정할 때 할 수 있는 설정도 하지 않는다

  • 코드를 사용하는 방법:
    • 이렇게 하자: 모든 설정을 완벽하게 한다.

    • 이렇게 하지 마세요: 소스코드에서 설정 수정이 필요하다.

INI 파일로 설정하는 예시

파일명은 logging_config.ini 로 합시다. 파일 포맷에 대한 자세한 설명은 로그 튜토리얼로그 설정 을 보시면 됩니다.

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