개발언어 Back-End/Python

Python Logging 사용 방법 정리

bluebamus 2025. 2. 13.

1. logging의 기본 개념

   1) 개요

      - 목적: 디버깅, 모니터링, 문제 해결을 위한 로그 기록
      - 특징: 다중 로거, 핸들러 및 포맷터 지원, 로그 레벨 설정, 필터링 등
      - 기본 구성 요소:

         - Logger: 로그 생성 및 관리, 핸들러 등록/제거, 레벨 설정 등
         - Handler: 로그 출력 대상 설정, 각종 핸들러(콘솔, 파일, 순환 파일 등) 제공
         - Formatter: 로그 메시지 출력 형식 지정 (날짜, 로거 이름, 레벨, 메시지 등)
         - Filter: 로그 레코드에 대해 추가 조건을 적용하여 출력 여부 결정

 

2. 로깅 시스템 구성 요소

   1) Logger

      - 정의: 로깅 시스템의 중심 객체로, 애플리케이션 코드에서 로그 메시지를 남길 때 사용한다.
      - 생성 방법: logging.getLogger(name)
         - name: 문자열로 로거의 이름을 지정한다. (빈 문자열은 root logger를 의미)

 

      - 로그 레벨
         -DEBUG (10): 상세한 정보, 문제 진단 시 사용
         -INFO (20): 정상 동작 확인을 위한 정보
         -WARNING (30): 예상치 못한 일이 발생했거나 가까운 미래에 문제가 발생할 수 있는 경우
         -ERROR (40): 심각한 문제로 인해 프로그램의 일부 기능이 동작하지 않는 경우
         -CRITICAL (50): 프로그램이 실행을 계속할 수 없는 심각한 에러

 

      - 로거 계층 구조

         - 로거는 계층적 구조를 가지며, 점(.)으로 구분된 이름을 사용한다.

logger = logging.getLogger('main.sub.module')

 

      - 로거 생성 및 설정

import logging

# 기본 로거 설정
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='app.log'
)

# 로거 생성
logger = logging.getLogger(__name__)

 

   2) Handler

      - 정의: Logger에서 생성된 로그 메시지를 최종 목적지(콘솔, 파일, 네트워크 등)로 전송하는 객체이다.

      - 종류:

         - StreamHandler: 콘솔(표준 출력 등)에 로그 출력
         - FileHandler: 파일에 로그 기록
         - RotatingFileHandler, TimedRotatingFileHandler: 파일 크기나 시간에 따라 로그 파일을 순환하며 관리
         - 기타: SMTPHandler, SysLogHandler, HTTPHandler 등

 

      - 핸들러 설정 예시

import logging
from logging.handlers import RotatingFileHandler

# 파일 핸들러 생성
file_handler = RotatingFileHandler(
    'app.log',
    maxBytes=1024*1024,  # 1MB
    backupCount=5
)
file_handler.setLevel(logging.INFO)

# 콘솔 핸들러 생성
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

# 포맷터 설정
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 로거에 핸들러 추가
logger = logging.getLogger(__name__)
logger.addHandler(file_handler)
logger.addHandler(console_handler)

 

   3) Formatter

      - 정의: 로그 메시지의 출력 형식을 지정한다.
      - 역할: 날짜/시간, 로그 레벨, 메시지, 로거 이름 등 다양한 정보를 포함하도록 포맷팅 한다.

 

      - 포멧터 설정

# 포맷터 설정
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

 

   4) Filter

      - 정의: 로깅 레코드가 최종 출력되기 전에 추가적인 조건으로 걸러낼 수 있는 객체이다.
      - 용도: 특정 모듈이나 로그 레벨, 또는 사용자 정의 조건에 따라 로그 메시지를 선택적으로 출력한다.

 

      - 기본 필터

class InfoFilter(logging.Filter):
    def filter(self, record):
        return record.levelno == logging.INFO

logger.addFilter(InfoFilter())

 

      - 필터 적용 예시

def email_filter(record):
    return record.levelno >= logging.ERROR

handler = logging.StreamHandler()
handler.addFilter(email_filter)

 

3. Logger 클래스

   1) 주요 메소드 및 파라미터

      1. logging.getLogger(name)

         - 설명: 지정된 이름을 가진 Logger 객체를 반환합니다. 동일한 이름을 사용하면 동일한 인스턴스가 반환된다.

         - 파라미터: name (문자열)

 

      2. Logger.setLevel(level)

         - 설명: Logger의 최소 로그 레벨을 설정한다. 이 레벨보다 낮은 로그는 무시된다.
         - 파라미터: level (예: logging.DEBUG, logging.INFO 등)

 

      3. 로그 메시지 기록 메소드:

         - Logger.debug(msg, *args, **kwargs)
         - Logger.info(msg, *args, **kwargs)
         - Logger.warning(msg, *args, **kwargs)
         - Logger.error(msg, *args, **kwargs)
         - Logger.critical(msg, *args, **kwargs)
         - Logger.exception(msg, *args, exc_info=True, **kwargs)
            - exception 메소드는 예외 정보를 자동으로 포함하여 ERROR 레벨 로그를 남긴다.

try:
    # 위험한 작업
    raise ValueError("잘못된 값")
except Exception as e:
    logger.exception("에러 발생")  # 자동으로 스택 트레이스 포함
    # 또는
    logger.error("에러 발생: %s", str(e), exc_info=True)

 

      4. Logger.log(level, msg, *args, **kwargs)

         - 설명: 지정한 레벨로 로그 메시지를 기록한다.

         - 파라미터:

            - level: 로그 레벨 (정수 값 또는 상수)

            - msg: 로그 메시지
            - *args, **kwargs: 메시지 포맷에 사용될 추가 인자

 

      5. Handler 관리 메소드:

         - Logger.addHandler(handler)

            - Logger에 핸들러를 추가한다.
         - Logger.removeHandler(handler)
            - Logger에서 핸들러를 제거한다.
         - Logger.hasHandlers()
            - Logger에 등록된 핸들러가 있는지 여부를 반환한다.

 

      6. 기타 메소드:

         - 내부적으로 사용되는 makeRecord(), findCaller() 등은 로그 레코드 생성에 관여하지만 일반 사용자 수준에서는 주로 위의 메소드들을 사용한다.

 

      7. extra 파라미터:

logger.info('메시지', extra={'ip': '123.45.67.89'})

 

4. Handler 클래스

   - Handler는 로그 메시지를 특정 출력 대상으로 전달하는 역할을 한다.

 

   1) 공통 메소드 및 파라미터

      - Handler.setLevel(level)

         - 설명: 해당 핸들러의 로그 레벨을 설정한다.
         - 파라미터: level (예: logging.DEBUG, logging.INFO 등)

 

      - Handler.setFormatter(fmt)

         - 설명: 로그 메시지의 출력 형식을 지정하는 Formatter 객체를 설정한다.
         - 파라미터: fmt (Formatter 인스턴스)

 

      - Handler.addFilter(filter)

         - 설명: 특정 조건에 따라 로그 메시지의 출력 여부를 결정할 수 있는 Filter 객체를 추가한다.
         - 파라미터: filter (Filter 인스턴스)

 

      - Handler.removeFilter(filter)

         - 설명: 추가된 Filter 객체를 제거한다.

 

      - 핸들러 처리 메소드:

         - Handler.handle(record)

            - 로그 레코드를 처리하며, 레벨 및 필터 조건 등을 확인한다.
         - Handler.emit(record)

            - 실제로 로그 메시지를 출력하는 메소드로, 각 Handler마다 구현이 다르다.

         - Handler.handleError(record)

            - 로그 출력 중 발생한 예외를 처리한다.

         - Handler.close()

            - 핸들러를 종료하고 자원을 정리한다.

 

   2) 주요 핸들러 종류와 생성자 파라미터

      1. StreamHandler

         - 설명: 콘솔이나 파일 객체와 같은 스트림으로 로그 출력

         - 생성자: logging.StreamHandler(stream=None)

            - stream: 출력 대상 스트림 (기본값은 sys.stderr)

 

      2. FileHandler

         - 설명: 로그 메시지를 파일에 기록
         - 생성자: logging.FileHandler(filename, mode='a', encoding=None, delay=False)
            - filename: 로그를 저장할 파일 이름
            - mode: 파일 열기 모드 (기본 'a' – append)
            - encoding: 파일 인코딩
            - delay: True일 경우 실제 파일 열기를 지연

 

      3. RotatingFileHandler

         - 설명: 파일 크기가 일정 수준에 도달하면 새 파일로 순환하며 로그 기록
         - 생성자:

            - maxBytes: 파일 최대 크기 (바이트 단위)
            - backupCount: 보관할 백업 파일 수

logging.handlers.RotatingFileHandler(
    filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False
)

 

       4. TimedRotatingFileHandler

         - 설명: 시간 간격에 따라 로그 파일을 순환하며 관리
         - 생성자:

            - when: 순환 기준 시간 단위 (예: 's', 'm', 'h', 'd', 'midnight', 'W0'-'W6')
            - interval: 시간 간격
            - backupCount: 보관할 백업 파일 수
            - utc: UTC 시간을 사용할지 여부

logging.handlers.TimedRotatingFileHandler(
    filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False
)

 

5. Formatter 클래스

   - Formatter는 로그 메시지의 최종 출력 형식을 지정한다.

 

   1) 생성자 및 파라미터

      - Formatter(fmt=None, datefmt=None, style='%')
         - fmt: 로그 메시지 형식 문자열
            - 예: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
         - datefmt: 날짜/시간 형식 문자열
            - 예: '%Y-%m-%d %H:%M:%S'
         - style: 포맷 스타일 지정 ('%', '{', 또는 '$')

 

   2) 주요 메소드

      - format(record)

         - 설명: 전달된 로그 레코드(LogRecord)를 지정된 형식 문자열에 따라 포맷하여 문자열로 반환
      - formatTime(record, datefmt=None)
         - 설명: 로그 레코드의 생성 시간을 지정한 날짜 형식으로 포맷
      - formatException(exc_info)
         - 설명: 예외 정보(exc_info)를 포맷하여 문자열로 반환

 

6. Filter 클래스

   - Filter는 Logger 또는 Handler에 추가되어 특정 조건에 맞는 로그 레코드만 통과시키는 역할을 한다.

 

   1) 생성자 및 기본 사용법

      - Filter(name='')
         - 설명: name 매개변수를 지정하면 해당 이름(또는 이름 접두사)을 가진 로거에 대해서만 필터링이 적용된다.

         - 예: Filter('my_module')는 로거 이름이 'my_module' 또는 그 하위인 경우에 적용된다.

   

      2) 주요 메소드

         - filter(record)
            - 설명: 로그 레코드를 인자로 받아, 해당 레코드를 출력할지 여부를 결정하는 불리언 값을 반환한다.
            - 반환 값: True이면 로그 메시지가 출력되고, False이면 무시된다.

 

7. 고급 설정

   1) 로깅 설정 파일 사용

      - fileConfig(): INI 형식의 구성 파일을 사용해 로깅 설정을 불러온다.
      - dictConfig(): 파이썬 딕셔너리 형태로 로깅 설정을 구성할 수 있다.

         - 두 방법 모두 여러 로거, 핸들러, 포맷터, 필터를 한 번에 설정할 수 있어 복잡한 로깅 환경에 유용하다.

 

   2) 커스텀 로깅

      - 사용자 정의 Logger/Handler/Formatter/Filter: 필요에 따라 기본 클래스를 상속하여 기능을 확장할 수 있다.
      - 로깅 레벨 추가: logging.addLevelName(level, levelName)를 통해 사용자 정의 로그 레벨을 추가할 수 있다.

 

8. 예제 코드

   - 다음은 Logger, Handler, Formatter, Filter를 활용한 예제 코드이다.

   

   - 주요 설명:

      - Logger 설정: myLogger 이름의 Logger를 생성하고 레벨을 DEBUG로 설정
      - Handler 구성: 콘솔 출력을 위한 StreamHandler와 파일 출력을 위한 FileHandler를 각각 생성 후 레벨과 Formatter를 설정
      - Filter 적용: 사용자 정의 Filter를 통해 메시지 내에 특정 단어("SECRET")가 포함된 경우 로그를 걸러냄
      - 로그 메시지 기록: 각 레벨별로 메시지를 기록, 예외 발생 시 logger.exception()을 활용하여 스택 트레이스까지 출력

import logging
import logging.handlers

# 사용자 정의 Filter: 메시지에 특정 단어가 포함되면 로그 출력하지 않음
class NoKeywordFilter(logging.Filter):
    def __init__(self, keyword, name=""):
        super().__init__(name)
        self.keyword = keyword

    def filter(self, record):
        # 로그 메시지에 지정된 keyword가 없으면 True 반환
        return self.keyword not in record.getMessage()

# 1. Logger 객체 생성 (이름이 'myLogger'인 로거)
logger = logging.getLogger('myLogger')
logger.setLevel(logging.DEBUG)  # DEBUG 레벨 이상 모든 메시지 기록

# 2. StreamHandler 생성 (콘솔 출력)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)

# 3. FileHandler 생성 (파일에 로그 기록)
file_handler = logging.FileHandler('app.log', mode='a', encoding='utf-8')
file_handler.setLevel(logging.INFO)  # INFO 이상 메시지만 기록

# 4. Formatter 생성 (출력 형식 지정)
formatter = logging.Formatter(
    fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
# 각 핸들러에 Formatter 설정
stream_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# 5. Filter 생성 및 핸들러에 추가 (예: "SECRET" 단어가 포함된 메시지 필터링)
keyword_filter = NoKeywordFilter(keyword='SECRET')
stream_handler.addFilter(keyword_filter)
file_handler.addFilter(keyword_filter)

# 6. Logger에 핸들러 추가
logger.addHandler(stream_handler)
logger.addHandler(file_handler)

# 7. 다양한 로그 메시지 기록
logger.debug("이 메시지는 디버그 정보입니다.")
logger.info("정보 메시지입니다.")
logger.warning("경고 메시지입니다.")
logger.error("오류 메시지입니다.")
logger.critical("치명적인 메시지입니다.")

# 예외 발생 시 exception 메소드 사용
try:
    1 / 0
except Exception as e:
    logger.exception("예외가 발생했습니다: %s", e)

# 필터 테스트: "SECRET" 단어가 포함된 메시지는 출력되지 않음
logger.info("이 메시지에는 SECRET 단어가 포함되어 있습니다. (출력되지 않음)")

 

9. 실무 예제 코드

   - 아래는 FastAPI 애플리케이션에서 파이썬 표준 logging 모듈을 활용하여 세 가지 “로그 뷰”를 구성하는 샘플 코드이다.

 

   1) 각 로그 뷰는 아래와 같이 동작한다.

      - console_view: 모든 레벨의 로그를 콘솔에 출력한다.
      - logfile_view: 오직 ERROR 레벨의 로그만 기록하며, 로그 파일명은 _logfile.txt_이다. (매일 자정마다 새 파일로 교체)
      - logfile_with_console: 오직 INFO 레벨의 로그만 기록하는데, 파일(log_with_console.txt)과 콘솔 모두에 출력된다. (파일은 매일 자정마다 새로 생성)

      - 또한, 실무에서 자주 사용하는 설정(로그 포맷, 레벨 설정, 파일 핸들러의 일별 회전 등)과 로그 필터를 직접 구현하여 정확한 레벨의 로그만 남길 수 있도록 한다.

 

   2) 주요 설정 및 메소드 설명

      1. Logger 생성 및 레벨 설정

         - logging.getLogger(name)을 통해 각 로그 뷰별로 별도의 로거를 생성한다.
         - setLevel() 메소드를 사용하여 해당 로거가 처리할 최소 로그 레벨을 지정한다.

 

      2. Handler 생성

         - StreamHandler: 콘솔(표준 출력)로 로그를 출력한다.
         - TimedRotatingFileHandler: 지정한 시간(여기서는 매일 자정)을 기준으로 파일을 교체하며 로그를 기록한다.

 

      3. Formatter 설정

         - logging.Formatter를 이용해 로그 출력 형식을 지정한다. (여기서는 날짜, 로거명, 로그 레벨, 메시지를 포함)

 

      4. 필터 적용

         - ExactLevelFilter를 구현하여 핸들러에 오직 특정 레벨의 로그만 통과시키도록 한다.
         - 예를 들어, logfile_view 핸들러에는 ExactLevelFilter(logging.ERROR)를 추가하여 에러 메시지만 기록한다.

 

      5. 핸들러 추가

         - 각 로거에 addHandler()를 호출하여 설정한 핸들러들을 추가한다.

import logging
from logging.handlers import TimedRotatingFileHandler
from fastapi import FastAPI

# -------------------------------------------------------------------
# 커스텀 필터 클래스: 특정 레벨의 로그만 통과시키도록 구현
# -------------------------------------------------------------------
class ExactLevelFilter(logging.Filter):
    def __init__(self, level):
        super().__init__()
        self.level = level

    def filter(self, record):
        # record.levelno가 지정한 레벨과 정확히 일치할 때만 True 리턴
        return record.levelno == self.level

# -------------------------------------------------------------------
# 1. console_view: 모든 로그를 콘솔에 출력
# -------------------------------------------------------------------
logger_console = logging.getLogger("console_view")
logger_console.setLevel(logging.DEBUG)  # 모든 로그 레벨을 처리하도록 설정

# 콘솔 핸들러 생성 및 포맷터 설정
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)  # 모든 로그 레벨 출력
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)

# console_view에 핸들러 추가
logger_console.addHandler(console_handler)

# -------------------------------------------------------------------
# 2. logfile_view: ERROR 레벨 로그만 logfile.txt에 기록 (매일 자정 교체)
# -------------------------------------------------------------------
logger_error_file = logging.getLogger("logfile_view")
logger_error_file.setLevel(logging.ERROR)

# TimedRotatingFileHandler를 사용하여 하루마다 새 파일 생성
error_file_handler = TimedRotatingFileHandler(
    filename="logfile.txt",    # 로그 파일명
    when="midnight",           # 매일 자정에 회전
    interval=1,                # 1일 간격
    backupCount=7,             # 최근 7일분 로그 파일 유지 (필요에 따라 조정)
    encoding="utf-8"
)
error_file_handler.setLevel(logging.ERROR)
# 오직 ERROR 레벨만 기록하도록 필터 적용
error_file_handler.addFilter(ExactLevelFilter(logging.ERROR))
error_file_handler.setFormatter(formatter)

logger_error_file.addHandler(error_file_handler)

# -------------------------------------------------------------------
# 3. logfile_with_console: INFO 레벨 로그를 파일(log_with_console.txt)과 콘솔 모두에 출력
# -------------------------------------------------------------------
logger_info = logging.getLogger("logfile_with_console")
logger_info.setLevel(logging.INFO)

# 파일 핸들러: 하루 단위 회전
info_file_handler = TimedRotatingFileHandler(
    filename="log_with_console.txt",  # 파일명 지정
    when="midnight",
    interval=1,
    backupCount=7,
    encoding="utf-8"
)
info_file_handler.setLevel(logging.INFO)
# 오직 INFO 레벨만 기록하도록 필터 적용
info_file_handler.addFilter(ExactLevelFilter(logging.INFO))
info_file_handler.setFormatter(formatter)

# 콘솔 핸들러: INFO 레벨 로그만 출력
info_console_handler = logging.StreamHandler()
info_console_handler.setLevel(logging.INFO)
info_console_handler.addFilter(ExactLevelFilter(logging.INFO))
info_console_handler.setFormatter(formatter)

# 두 핸들러 모두 logger_info에 추가
logger_info.addHandler(info_file_handler)
logger_info.addHandler(info_console_handler)

# -------------------------------------------------------------------
# FastAPI 애플리케이션 구성
# -------------------------------------------------------------------
app = FastAPI()

@app.get("/")
def read_root():
    # 각 로거를 사용하여 로그 메시지 남기기
    # console_view: 모든 로그가 콘솔에 출력됨
    logger_console.debug("DEBUG: console_view에서 출력되는 디버그 메시지")
    logger_console.info("INFO: console_view에서 출력되는 정보 메시지")
    logger_console.error("ERROR: console_view에서 출력되는 에러 메시지")

    # logfile_view: 오직 ERROR 레벨만 logfile.txt에 기록됨
    logger_error_file.error("ERROR: logfile_view에서 출력되는 에러 메시지")

    # logfile_with_console: 오직 INFO 레벨만 기록되어, 파일과 콘솔에 출력됨
    logger_info.info("INFO: logfile_with_console에서 출력되는 정보 메시지")

    return {"message": "로그가 설정되었습니다. 콘솔 및 파일 로그를 확인하세요."}

 

 - reference : 

https://docs.python.org/3/howto/logging.html

 

Logging HOWTO

Author, Vinay Sajip <vinay_sajip at red-dove dot com>,. This page contains tutorial information. For links to reference information and a logging cookbook, please see Other resources. Basic Logging...

docs.python.org

https://docs.python.org/3/library/logging.html

 

logging — Logging facility for Python

Source code: Lib/logging/__init__.py Important: This page contains the API reference information. For tutorial information and discussion of more advanced topics, see Basic Tutorial, Advanced Tutor...

docs.python.org

 

댓글