파이썬 로깅 시스템 심층 가이드

로깅 모듈 처리 흐름:

다음과 같은 주요 구성 요소들로 나뉩니다:

  • logger: 최상위 구성 요소로, 로그 메시지를 생성하는 역할을 합니다
  • logger.level을 통해 로그 메시지의 우선순위를 결정
  • logger.debug()/info()/warning()/error() 등의 메서드를 사용해 로그 출력
  • handler: logger를 통과한 로그 메시지가 전달되어 최종적으로 처리되는 장치
  • 각 handler는 고유한 level, formatter, 출력 스트림을 가짐
  • handler.level은 로그 메시지를 다시 필터링
  • handler.formatter는 로그 출력 형식을 결정

StreamHandler: 표준 출력 스트림으로 로그를 전송하는 일반적인 핸들러 FileHandler: 로그를 파일에 저장하는 핸들러

레벨 순서: NOTSET < DEBUG < INFO < WARNING < ERROR

지정된 레벨보다 높은 우선순위를 가진 로그만 처리됩니다

중요: 각 로그 메시지는 두 번의 필터링을 거칩니다. 첫 번째는 logger.level에서, 두 번째는 handler.level에서입니다. logger.level에서 걸러진 로그는 handler 처리 단계에 도달하지 않습니다. handler와 logger의 레벨은 서로 독립적입니다.

예제 1. 기본 로깅 사용법:

import logging
logging.debug('이 메시지는 출력되지 않습니다')
logging.info('이 메시지도 출력되지 않습니다')
logging.warning('이 메시지만 출력됩니다 - 기본 레벨이 WARNING이기 때문')

기본적으로 stderr로 출력됩니다

예제 2. 커스텀 출력을 포함한 기본 로깅 사용법:

import logging, sys
if __name__ == '__main__':
	log = logging.getLogger(__name__)
	logging.basicConfig(
        format="[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s >> %(message)s",
        datefmt="%Y/%m/%d %H:%M:%S",
        handlers=[logging.StreamHandler(sys.stdout), logging.FileHandler('app.log')],
    )
	log.setLevel(logging.INFO)

	log.info('애플리케이션 시작')
	log.error('오류 발생')

출력 결과:

[INFO|logging_example.py:11] 2023/05/15 14:30:22 >> 애플리케이션 시작
[ERROR|logging_example.py:12] 2023/05/15 14:30:22 >> 오류 발생

동시에 'app.log' 파일에도 저장됩니다

주요 기본값들

  • root logger가 존재하며, 사용자 정의 logger의 부모 역할을 합니다
  • root의 기본 레벨은 WARNING이며, handler는 없습니다
  • logger의 모든 부모(root 포함)에 handler가 없으면 기본 전역 handler가 사용됩니다. 이 handler의 레벨은 WARNING이며 stderr로 출력합니다
  • logger에 레벨이 명시되지 않으면, 부모 중 첫 번째로 발견되는 NOTSET이 아닌 레벨을 사용합니다. 보통은 root의 WARNING 레벨입니다. 모든 부모에 레벨이 없으면 NOTSET(가장 낮은 레벨)을 사용합니다
  • basicConfig 함수는 root logger의 속성을 설정합니다. root에 handler가 이미 존재하면 basicConfig는 아무 효과가 없습니다
  • 수동으로 생성된 handler의 기본 레벨은 NOTSET이며, 수동으로 생성된 logger의 기본 레벨도 NOTSET입니다. 하지만 부모에 NOTSET이 아닌 레벨이 있으면 부모의 레벨이 우선적으로 사용됩니다 (예: root의 WARNING)

위 예제 2에서는 root handler에 두 개의 handler를 설정했습니다. 하나는 stdout으로, 다른 하나는 파일로 출력합니다. 두 handler가 동일한 formatter를 공유합니다. 사용자 정의 logger는 직접 handler를 가지지 않으며, 최종적으로 root의 handler를 재귀적으로 사용합니다.

예제 3. INFO와 WARNING 레벨 로그를 다른 위치로 분리 출력

import logging
from sys import stdout, stderr
if __name__ == "__main__":
	log_format="[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s >> %(message)s"
	date_format="%Y/%m/%d %H:%M:%S"
	formatter = logging.Formatter(log_format, date_format)

	# stdout용 로거
	app_logger = logging.getLogger("app")
	# NOTSET으로 설정하면 root의 WARNING 레벨이 우선 적용되므로 주의
	app_logger.setLevel(logging.DEBUG)
	std_handler = logging.StreamHandler(stdout)
	std_handler.setFormatter(formatter)
	app_logger.addHandler(std_handler)

	# stderr용 로거
	error_logger = logging.getLogger("app.errors")
	error_logger.setLevel(logging.WARNING)
	err_handler = logging.StreamHandler(stderr)
	err_handler.setFormatter(formatter)
	error_logger.addHandler(err_handler)

	# 테스트 로그
	app_logger.debug("디버그 메시지")
	app_logger.info("정보 메시지")
	error_logger.warning("경고 메시지")
	error_logger.error("오류 메시지")

올바른 로거 생성 방법

import logging
my_logger = logging.getLogger("my_application.module")

이름을 전달하는 것이 좋습니다. 이름을 전달하면 새로운 로거가 반환되며, 이름을 전달하지 않으면 root logger가 반환됩니다. root logger는 모든 로거의 부모입니다. root logger에 handler, formatter 등을 설정하면 다른 모듈의 로거에도 영향을 미칩니다 (해당 로거가 직접 formatter, handler 등을 설정한 경우는 제외). 이는 logging.getLogger("xxx")로 얻은 로거는 기본적으로 handler와 formatter가 없으며, 부모의 handler와 formatter를 사용하기 때문입니다.

포맷 문자열 형식

권장되는 형식:

format="[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s >> %(message)s"

파일 이름과 줄 번호를 표시하여 IDE에서 해당 위치로 직접 이동할 수 있습니다. 이는 Hugging Face Transformers 라이브러리에서 사용하는 기본 로깅 형식이기도 합니다.

태그: 파이썬 로깅 로깅-모듈 로깅-시스템 로거

5월 23일 06:53에 게시됨