본문으로 건너뛰기
김신건의 로그

[Python] logging: 로거, 핸들러, 포매터

· 수정 · 📖 약 2분 · 878자/단어 #python #logging #stdlib
python logging, logger, log handler, log formatter, logging.config, structured logging

정의

logging은 Python 표준 로깅 프레임워크. logger - handler - formatter - filter 4구성 요소로 유연한 로그 라우팅을 구현. 단순 사용은 한 줄로, 복잡한 구성은 dict/yaml로.

빠른 시작

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.debug("debug")
logger.info("info")
logger.warning("warning")
logger.error("error", extra={"user_id": 1})
logger.critical("critical")

basicConfig는 한 번만 동작 (이미 핸들러 있으면 무시). 라이브러리에선 호출 금지: 사용자의 logging 설정을 침범한다.

레벨

레벨숫자용도
DEBUG10디버그 정보
INFO20일반 정보
WARNING30경고 (기본 임계값)
ERROR40에러
CRITICAL50치명적

logger와 handler 각자 레벨을 가진다. 둘 다 통과해야 출력.

logger 계층

logger 이름은 .으로 계층 표현. 부모-자식 관계.

log = logging.getLogger("myapp")
log_api = logging.getLogger("myapp.api")
log_db = logging.getLogger("myapp.db")

핸들러는 일반적으로 루트 logger 또는 앱 최상위 logger에 부착. 자식 logger의 메시지가 propagate되어 부모 핸들러로 흐름.

log_api.info("...")    # myapp.api → myapp → root 로 전파

logging.getLogger(__name__) 패턴이 표준: 모듈 경로 그대로 logger 이름이 됨.

핸들러 설정

import logging
import sys

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

# 콘솔 핸들러
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.INFO)

# 파일 핸들러
file = logging.FileHandler("app.log")
file.setLevel(logging.DEBUG)

# 포매터
fmt = logging.Formatter(
    "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
console.setFormatter(fmt)
file.setFormatter(fmt)

logger.addHandler(console)
logger.addHandler(file)

logger.info("hello")

INFO 이상은 콘솔, DEBUG 이상은 파일에 기록.

포매터 변수

변수의미
%(asctime)s타임스탬프
%(name)slogger 이름
%(levelname)s레벨 이름
%(message)s메시지
%(funcName)s호출 함수
%(filename)s파일
%(lineno)d줄번호
%(pathname)s절대 경로
%(process)dPID
%(thread)d스레드 ID
%(threadName)s스레드 이름

dictConfig: 선언적 설정

import logging.config

config = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "default": {
            "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        },
        "json": {
            "()": "pythonjsonlogger.json.JsonFormatter",
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
            "level": "INFO",
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": "app.log",
            "formatter": "json",
            "maxBytes": 10485760,
            "backupCount": 5,
            "level": "DEBUG",
        },
    },
    "loggers": {
        "myapp": {
            "handlers": ["console", "file"],
            "level": "DEBUG",
            "propagate": False,
        },
    },
}

logging.config.dictConfig(config)

YAML/JSON 파일에 두고 dictConfig(yaml.safe_load(open("logging.yaml")))처럼 로드 가능. Django, Flask 같은 프레임워크도 이 방식.

자주 쓰이는 핸들러

핸들러용도
StreamHandlerstdout/stderr
FileHandler단일 파일
RotatingFileHandler크기 기준 로테이션
TimedRotatingFileHandler시간 기준 (매일 등)
SysLogHandlersyslog
SMTPHandler이메일 알림 (스팸 위험)
HTTPHandlerHTTP POST
QueueHandler + QueueListener멀티스레드/프로세스 안전
NullHandler라이브러리 기본 (소비자가 설정 안 했을 때 경고 방지)

라이브러리 로깅 가이드

라이브러리 코드는:

  1. logger = logging.getLogger(__name__) 모듈마다
  2. 핸들러 부착 금지 (사용자 책임)
  3. logging.NullHandler 추가 (사용자 미설정 시 경고 방지)
# library_init.py
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

예외 로깅

try:
    risky()
except Exception:
    logger.exception("something failed")
    # 자동으로 traceback 포함, level=ERROR

logger.exception(msg) = logger.error(msg, exc_info=True).

구조화 로깅

표준 logger는 문자열 위주. 구조화(JSON) 로그는:

pip install python-json-logger

또는 structlog, loguru 같은 라이브러리 사용. ELK/Datadog/CloudWatch 같은 로그 시스템과 잘 통합.

logger.info("user_login", extra={"user_id": 42, "ip": "1.2.3.4"})

extra로 임의 필드 추가. 포매터가 JSON이면 그대로 직렬화.

성능

  • 로그 호출은 디스에이블 레벨도 함수 호출 비용 있음. hot loop에선:
if logger.isEnabledFor(logging.DEBUG):
    logger.debug("expensive: %s", expensive_calc())
  • f-string 대신 %s 포매팅 권장 (debug 비활성화 시 포매팅 안 함)
logger.debug("user %s logged in", user.id)    # OK
logger.debug(f"user {user.id} logged in")     # 항상 포매팅됨
  • 매우 hot한 경로: QueueHandler로 비동기 처리
printlogging
레벨XO
라우팅stdout 고정다중 destination
포맷수동자동
비활성화코드 수정설정 변경
시간/소스 정보수동자동

스크립트 일회성은 print, 프로덕션은 항상 logging.

함정

1. logging.info()와 root logger

import logging
logging.info("hello")   # root logger 사용 + 기본 핸들러 없음 = 출력 안 됨

basicConfig() 호출 또는 명시적 logger 사용 필수.

2. 모듈 import 시 핸들러 부착

# myapp/foo.py
import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler())   # 다시 import되면 중복 추가

핸들러 부착은 앱 시작 한 번만. 모듈 import time 부착 금지.

3. 사용자 입력을 로그 메시지로

logger.info(user_input)    # 로그 인젝션 가능

신뢰할 수 없는 입력은 escape하거나 분리된 필드(extra)로.

대안

  • loguru: 더 친근한 API, JSON 출력, 자동 컨텍스트. 단일 라이브러리 사용.
  • structlog: 구조화 로그 우선. 컨텍스트 바인딩 강력.
  • rich.logging: 컬러풀한 콘솔 로그.

표준 logging이 라이브러리 인터페이스라 다른 패키지와 호환성이 가장 좋음.

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기