본문 바로가기

프로그래밍/Python

[파이썬 / 기본] Logging

728x90
반응형

1. 로깅 시작하기 및 기본 레벨 이해하기

1.1 5가지 로깅 레벨

  • logging 모듈은 이벤트의 심각도(Severity)를 나타내는 다섯 가지의 로그 레벨을 제공
레벨 이름
심각도
설명
DEBUG
가장 낮음
상세 정보, 개발 시 주로 사용
INFO
보통
예상대로 작동하는 것에 대한 확인
WARNING
보통
예상치 못한 상황 발생, 하지만 소프트웨어는 정상 작동 중 (기본 출력 레벨)
ERROR
높음
심각한 문제로 인해 일부 기능이 수행될 수 없음
CRITICAL
가장 높음
프로그램 자체가 계속 실행될 수 없는 심각한 오류

1.2 기본 동작 확인

import logging

logging.debug("디버그 메시지 - 출력되지 않음")
logging.info("정보 메시지 - 출력되지 않음")
logging.warning("경고 메시지 - 출력됨")
logging.error("오류 메시지 - 출력됨")
logging.critical("치명적 오류 메시지 - 출력됨")
 

2. 로깅의 기본 구성 변경하기 (basicConfig)

  • 기본 설정은 warning 레벨부터 로깅하고, 출력 포맷이 단순
  • 만약 debug 메시지까지 보고 싶거나 출력 형식을 변경하고 싶다면 logging.basicConfig() 함수를 사용해야 합니다.
  • 이 설정은 일반적으로 logging 모듈을 가져온 직후에 설정하는 것이 가장 효율적
  • basicConfig를 통해 레벨(level), 형식(format), 날짜 형식(date format) 등의 인수를 지정할 수 있습니다.

2.1 로깅 레벨 변경

  • 로깅 레벨을 logging.debug로 설정하면 모든 메시지(DEBUG, INFO, WARNING, ERROR, CRITICAL)가 출력됩니다.

2.2 출력 형식 사용자 지정

  • format 인수에 문자열을 전달하여 출력 형식을 상세하게 지정 가능
  • 이 문자열 안에는 다음과 같은 로그 레코드 속성(Lock Record Attributes)을 포함 가능
    • %(name)s: 로거 이름
    • %(asctime)s: 시간
    • %(levelname)s: 레벨 이름
    • %(message)s: 실제 메시지
  • 또한 datefmt 인수를 사용하여 시간 형식을 지정할 수 있습니다 (예: %m은 월, %d는 일, %H는 시).

import logging
import time

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y/%m/%d %H:%M:%S'
)

logging.debug("이제 디버그 메시지가 보여요!")
logging.info("정보 메시지 예시")
logging.warning("경고 메시지 예시")
  • 기본적으로 로거 이름(%(name)s)을 지정하지 않으면 'root' 로거가 사용됩니다.
 

3. 여러 모듈에서 로깅 사용하기 (로거 계층 구조)

  • 애플리케이션이 여러 모듈로 구성될 때, 모든 곳에서 'root' 로거를 사용하는 것은 좋지 않습니다.
  • 모듈은 자신만의 로거를 생성하는 것이 모범 사례

3.1 모듈별 로거 생성

  • 각 모듈에서 로거를 생성하려면 logging.getLogger()를 사용하고, 로거 이름으로 __name__ 전역 변수를 사용하는 것이 좋습니다:
# helper.py
import logging
logger = logging.getLogger(__name__)  # 예: helper

def do_something():
    logger.debug("헬퍼 디버그")
    logger.info("헬퍼 정보")
    
    
# app.py
import logging
from helper import do_something

logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s')

logger = logging.getLogger(__name__)  # 예: app
logger.info("앱 시작")
do_something()
  • 이렇게 생성된 로거는 루트 로거에서 시작하는 계층 구조에 추가

3.2 로깅 전파 제어

  • 새로운 로거는 메시지를 기본(루트) 로거로 전파합니다 (propagate의 기본값은 True).
  • 만약 이 전파를 막고 싶다면 다음과 같이 설정할 수 있습니다:

import logging

logger = logging.getLogger("myapp.module")
logger.setLevel(logging.DEBUG)
logger.propagate = False  # 루트로 전파하지 않음

# 직접 핸들러를 붙임
stream_h = logging.StreamHandler()
stream_h.setLevel(logging.DEBUG)
stream_h.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
logger.addHandler(stream_h)

logger.info("이 메시지는 module 로거에서만 처리되고 루트로 안감")
  • 전파를 비활성화하면, 기본 로거에는 메시지가 전달되지 않음
 

4. 고급 구성 — 핸들러 (Handler) 사용하기

  • 핸들러 객체는 로그 메시지를 실제 대상(파일, 스트림, 이메일 등)으로 보내는 역할을 담당
  • 하나의 로거에 여러 개의 핸들러를 붙여서, 로그 메시지를 여러 대상으로 동시에 보내는 것이 가능

4.1 핸들러 설정 및 로거에 추가

  • 예를 들어, warning 메시지는 콘솔(스트림)에, error 메시지는 파일에만 로깅하고 싶다면 핸들러를 사용합니다:
    • 1. 로거 생성: logging.getLogger(__name__)을 사용
    • 2. 핸들러 생성: StreamHandlerFileHandler를 생성
    • 3. 핸들러별 레벨/포맷 설정: 각 핸들러에 대해 setlevel()setFormatter()를 호출
    • 4. 로거에 핸들러 추가: logger.addHandler()를 사용하여 핸들러를 로거에 추가

import logging

logger = logging.getLogger("example")
logger.setLevel(logging.DEBUG)
logger.propagate = False

# 콘솔 핸들러는 WARNING 이상만 출력
stream_h = logging.StreamHandler()
stream_h.setLevel(logging.WARNING)
stream_h.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))

# 파일 핸들러는 ERROR 이상만 로그 파일에 기록
file_h = logging.FileHandler('error.log', encoding='utf-8')
file_h.setLevel(logging.ERROR)
file_h.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

logger.addHandler(stream_h)
logger.addHandler(file_h)

logger.info("정보 - 어디에도 출력되지 않음")
logger.warning("경고 - 콘솔에 출력")
logger.error("오류 - 콘솔과 error.log에 기록")
  • 콘솔: WARNING, ERROR 메시지 표시
  • 파일 error.log : ERROR 메시지에 대한 타임스탬프 포함 로그 행 존재
 

5. 문제 해결을 위한 스택 추적 캡처하기

  • 예외(Exception) 발생 시 스택 추적(Stack Trace) 정보를 로깅하는 것은 문제 해결에 매우 유용

5.1 exc_info=True 사용

  • 예외를 try-except 블록으로 잡을 때, logging.error() 메서드에 exc_info=True 인수를 설정하면 스택 추적이 로그에 포함

import logging

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

try:
    my_list = [16, 18]   # 올바른 리스트 초기화
    value = my_list[19]  # IndexError 발생
except IndexError as e:
    logging.error("인덱스 오류 발생 (exc_info 포함)", exc_info=True)
 

6. 대규모 애플리케이션을 위한 파일 로테이션 핸들러

  • 로그 메시지가 많은 대규모 애플리케이션의 경우, 로그 파일 크기가 무한정 커지는 것을 방지해야 합니다.
  • 이럴 때 **로테이션 파일 핸들러(Rotating File Handler)**를 사용합니다.

6.1 크기 기반 로테이션 (RotatingFileHandler)

  • RotatingFileHandler를 사용하면 최대 바이트(maxBytes)를 지정하여 파일 크기가 이 한도를 초과하면 새 로그 파일로 롤오버(rollover)
  • backupCount를 지정하여 보관할 백업 파일의 개수를 제한

import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("rotating_example")
logger.setLevel(logging.DEBUG)

handler = RotatingFileHandler('app.log', maxBytes=1024, backupCount=3, encoding='utf-8')
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))

logger.addHandler(handler)

# 테스트: 많은 메시지를 찍어 파일이 롤오버되도록 함
for i in range(1000):
    logger.debug(f"디버그 메시지 {i}")

6.2 시간 기반 로테이션 (TimedRotatingFileHandler)

  • 애플리케이션이 장기간 실행되는 경우, 시간 간격(when)을 기준으로 로그 파일을 로테이션
  • when 인수에 초(S), 분(M), 시(H), 일(D), 자정(midnight) 또는 요일(W0: 월요일, W1: 화요일 등)을 지정

import logging
from logging.handlers import TimedRotatingFileHandler
import time

logger = logging.getLogger("timed_example")
logger.setLevel(logging.INFO)

handler = TimedRotatingFileHandler('time_app.log', when='S', interval=5, backupCount=3, encoding='utf-8')
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

# 테스트: 몇 초마다 로그를 찍어 파일이 변경되는지 확인
for i in range(20):
    logger.info(f"타임 로테이트 테스트 {i}")
    time.sleep(1)
 

7. 외부 설정 파일 사용하기

  • 로깅 구성을 코드 내부에 하드 코딩하는 대신, 별도의 파일(logging.conf, logging.ini)을 사용하여 로거, 핸들러, 포매터를 정의 가능
  • 이러한 외부 구성을 사용하려면 logging.config 모듈을 가져와 fileConfig 또는 dictConfig 메서드를 사용

 

# dictConfig 예시
import logging
import logging.config

LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "WARNING",
            "formatter": "standard",
            "stream": "ext://sys.stdout"
        },
        "errors_file": {
            "class": "logging.FileHandler",
            "level": "ERROR",
            "formatter": "standard",
            "filename": "dict_error.log",
            "encoding": "utf-8"
        }
    },
    "loggers": {
        "": {  # root logger
            "handlers": ["console", "errors_file"],
            "level": "DEBUG",
            "propagate": False
        },
        "myapp.special": {
            "handlers": ["console"],
            "level": "INFO",
            "propagate": False
        }
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("myapp.special")
logger.info("myapp.special의 정보(콘솔에만)")
logger.error("myapp.special의 오류(콘솔 + dict_error.log에 기록)")

 

# fileconfig
import logging
import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('simpleExample')  # conf에서 정의된 로거 이름
logger.error("fileConfig로 로깅 테스트")

 

 

https://github.com/JK-17/Python_Logging_Prac.git

 

GitHub - JK-17/Python_Logging_Prac

Contribute to JK-17/Python_Logging_Prac development by creating an account on GitHub.

github.com

 

 

*** 위 링크에서 확인 가능 ***

728x90
반응형