Python으로 개발을 하다 보면 애플리케이션의 동작을 추적하고 문제를 진단하기 위해 로깅이 필수적입니다. Python의 표준 logging 모듈은 1999년부터 Python에 포함되어 온 검증된 로깅 솔루션으로, 전 세계 수많은 프로덕션 환경에서 안정적으로 사용되고 있습니다.
Python 표준 logging 모듈이란?
Python 표준 logging 모듈은 Python 표준 라이브러리에 포함된 로깅 기능으로, 별도의 설치 없이 바로 사용할 수 있습니다. 이 모듈은 복잡한 로깅 요구사항을 충족할 수 있는 유연하고 강력한 기능들을 제공합니다.
기본 사용법
가장 간단한 형태의 logging 사용법부터 시작해보겠습니다.
import logging
# 기본 설정
logging.basicConfig(level=logging.INFO)
# 다양한 레벨의 로그 출력
logging.debug("디버그 메시지입니다")
logging.info("정보성 메시지입니다")
logging.warning("경고 메시지입니다")
logging.error("에러가 발생했습니다")
logging.critical("심각한 오류입니다")
실행 결과:
INFO:root:정보성 메시지입니다
WARNING:root:경고 메시지입니다
ERROR:root:에러가 발생했습니다
CRITICAL:root:심각한 오류입니다
DEBUG 메시지는 기본 레벨이 WARNING이기 때문에 출력되지 않습니다.
로그 레벨의 이해
logging 모듈은 5가지 로그 레벨을 제공합니다:
- DEBUG (10): 상세한 진단 정보, 주로 개발 중에 사용
- INFO (20): 일반적인 정보 메시지
- WARNING (30): 주의가 필요한 상황, 기본 레벨
- ERROR (40): 심각한 문제가 발생했지만 프로그램은 계속 실행
- CRITICAL (50): 매우 심각한 오류, 프로그램 종료 가능
import logging
# 현재 로그 레벨 확인
logger = logging.getLogger()
print(f"현재 로그 레벨: {logger.getEffectiveLevel()}") # 30 (WARNING)
# 레벨별 숫자값 확인
print(f"DEBUG: {logging.DEBUG}") # 10
print(f"INFO: {logging.INFO}") # 20
print(f"WARNING: {logging.WARNING}") # 30
print(f"ERROR: {logging.ERROR}") # 40
print(f"CRITICAL: {logging.CRITICAL}")# 50
Logger 객체 사용하기
실제 프로젝트에서는 Logger 객체를 직접 생성하여 더 세밀한 제어를 합니다.
import logging
# Logger 생성 (보통 모듈명을 사용)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 콘솔 핸들러 생성
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 포매터 생성
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(formatter)
# Logger에 핸들러 추가
logger.addHandler(console_handler)
# 로그 출력 테스트
logger.debug("이 메시지는 표시되지 않습니다")
logger.info("애플리케이션이 시작되었습니다")
logger.warning("메모리 사용량이 높습니다")
logger.error("데이터베이스 연결 실패")
실행 결과:
2024-05-29 14:30:15,123 - __main__ - INFO - 애플리케이션이 시작되었습니다
2024-05-29 14:30:15,124 - __main__ - WARNING - 메모리 사용량이 높습니다
2024-05-29 14:30:15,125 - __main__ - ERROR - 데이터베이스 연결 실패
파일에 로그 저장하기
프로덕션 환경에서는 로그를 파일에 저장하는 것이 일반적입니다.
기본 파일 로깅
import logging
# 파일 로깅 설정
logging.basicConfig(
filename='application.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
encoding='utf-8'
)
logging.info("애플리케이션 시작")
logging.warning("경고: 디스크 공간 부족")
logging.error("에러: 파일을 찾을 수 없습니다")
콘솔과 파일 동시 출력
import logging
# Logger 생성
logger = logging.getLogger('my_application')
logger.setLevel(logging.DEBUG)
# 콘솔 핸들러 (INFO 레벨 이상만)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 파일 핸들러 (모든 레벨)
file_handler = logging.FileHandler('debug.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
# 포매터 설정
console_formatter = logging.Formatter('%(levelname)s: %(message)s')
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
file_handler.setFormatter(file_formatter)
# 핸들러 추가
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# 테스트
logger.debug("상세 디버그 정보 (파일에만 저장)")
logger.info("일반 정보 (콘솔과 파일 모두)")
logger.error("오류 발생 (콘솔과 파일 모두)")
예외 로깅
예외 발생 시 스택 트레이스와 함께 로그를 남기는 것은 매우 중요합니다.
import logging
logging.basicConfig(level=logging.ERROR)
def divide_numbers(a, b):
try:
result = a / b
logging.info(f"{a} ÷ {b} = {result}")
return result
except ZeroDivisionError:
logging.exception("0으로 나누기 오류가 발생했습니다")
return None
except Exception as e:
logging.error(f"예상치 못한 오류: {e}", exc_info=True)
return None
# 테스트
divide_numbers(10, 2) # 정상 동작
divide_numbers(10, 0) # 예외 발생
실행 결과:
ERROR:root:0으로 나누기 오류가 발생했습니다
Traceback (most recent call last):
File "test.py", line 6, in <module>
result = a / b
ZeroDivisionError: division by zero
로그 로테이션
로그 파일이 너무 커지는 것을 방지하기 위해 로테이션 기능을 사용할 수 있습니다.
import logging
from logging.handlers import RotatingFileHandler
# Logger 설정
logger = logging.getLogger('rotating_logger')
logger.setLevel(logging.INFO)
# 로테이팅 파일 핸들러 (최대 1MB, 최대 5개 파일)
handler = RotatingFileHandler(
'app.log',
maxBytes=1024*1024, # 1MB
backupCount=5,
encoding='utf-8'
)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# 대량의 로그 생성 (테스트용)
for i in range(1000):
logger.info(f"로그 메시지 #{i}: 애플리케이션이 정상적으로 실행 중입니다")
설정 파일 활용
복잡한 로깅 설정은 설정 파일로 관리하는 것이 좋습니다.
logging.conf:
[loggers]
keys=root,myApp
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter,detailedFormatter
[logger_root]
level=WARNING handlers=consoleHandler [logger_myApp] level=DEBUG handlers=consoleHandler,fileHandler qualname=myApp propagate=0 [handler_consoleHandler] class=StreamHandler level=INFO formatter=simpleFormatter args=(sys.stdout,) [handler_fileHandler] class=FileHandler level=DEBUG formatter=detailedFormatter args=(‘application.log’, ‘a’, ‘utf-8’) [formatter_simpleFormatter] format=%(levelname)s: %(message)s [formatter_detailedFormatter] format=%(asctime)s – %(name)s – %(levelname)s – %(message)s
Python 코드:
import logging.config
# 설정 파일 로드
logging.config.fileConfig('logging.conf')
# Logger 사용
logger = logging.getLogger('myApp')
logger.debug("디버그 메시지")
logger.info("정보 메시지")
logger.error("에러 메시지")
실제 프로젝트에서의 활용 예시
다음은 웹 애플리케이션에서 logging을 활용하는 실제적인 예시입니다.
import logging
import logging.config
from datetime import datetime
# 로깅 설정
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'detailed': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
},
'simple': {
'format': '%(levelname)s: %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'simple'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'app.log',
'maxBytes': 10485760, # 10MB
'backupCount': 5,
'level': 'DEBUG',
'formatter': 'detailed',
'encoding': 'utf-8'
}
},
'loggers': {
'': { # root logger
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': False
}
}
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)
class UserService:
def __init__(self):
self.logger = logging.getLogger(f"{__name__}.UserService")
def create_user(self, username, email):
self.logger.info(f"새 사용자 생성 시도: {username}")
try:
# 사용자 생성 로직
if self.validate_email(email):
# 데이터베이스 저장 로직
self.logger.info(f"사용자 생성 완료: {username} ({email})")
return True
else:
self.logger.warning(f"잘못된 이메일 형식: {email}")
return False
except Exception as e:
self.logger.error(f"사용자 생성 실패: {username}", exc_info=True)
return False
def validate_email(self, email):
# 이메일 검증 로직
return '@' in email
# 사용 예시
user_service = UserService()
user_service.create_user("john_doe", "john@example.com")
user_service.create_user("jane_doe", "invalid-email")
표준 logging 모듈의 장단점
장점
안정성과 신뢰성: 20년 이상 검증된 안정적인 라이브러리로, 대규모 프로덕션 환경에서 널리 사용됩니다.
유연성: 복잡한 로깅 요구사항을 충족할 수 있는 다양한 핸들러, 포매터, 필터를 제공합니다.
표준화: Python 생태계의 표준이므로 대부분의 라이브러리와 프레임워크에서 호환됩니다.
별도 설치 불필요: Python 표준 라이브러리에 포함되어 있어 즉시 사용 가능합니다.
단점
복잡한 설정: 고급 기능을 사용하려면 상당한 설정이 필요합니다.
가독성: 기본 출력 형식이 다소 복잡하고 색상 지원이 기본적으로 제공되지 않습니다.
학습 곡선: Logger, Handler, Formatter 등의 개념을 이해해야 효과적으로 사용할 수 있습니다.
마무리
Python 표준 logging 모듈은 강력하고 유연한 로깅 솔루션입니다. 초기 설정이 다소 복잡할 수 있지만, 한 번 제대로 설정하면 안정적이고 확장 가능한 로깅 시스템을 구축할 수 있습니다. 특히 대규모 프로젝트나 팀 개발 환경에서는 표준 logging 모듈의 안정성과 호환성이 큰 장점이 됩니다.
다음에는 더 간편한 사용법을 제공하는 Loguru 라이브러리에 대해서도 살펴보겠습니다. 각각의 특성을 이해하고 프로젝트의 요구사항에 맞는 선택을 하시기 바랍니다.