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

[Django] Logging: LOGGING 설정과 구조화 로그

· 수정 · 📖 약 1분 · 428자/단어 #django #logging #observability
django logging, LOGGING dict, django log handler, structlog rails, json logging

정의

Django는 Python logging 모듈을 그대로 사용하나 LOGGING 설정 dict로 일괄 구성한다. 구조화 로그(JSON), 외부 서비스(Sentry, Datadog, CloudWatch) 연동, request ID 주입 등이 표준 패턴.

기본 설정

# settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "{asctime} {levelname} {name} {message}",
            "style": "{",
        },
        "simple": {
            "format": "{levelname} {message}",
            "style": "{",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "verbose",
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "filename": BASE_DIR / "logs" / "app.log",
            "maxBytes": 10 * 1024 * 1024,
            "backupCount": 5,
            "formatter": "verbose",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
    },
    "loggers": {
        "django.request": {
            "handlers": ["console", "file"],
            "level": "WARNING",
            "propagate": False,
        },
        "django.db.backends": {
            "level": "WARNING",    # DEBUG로 모든 SQL 로그
        },
        "myapp": {
            "handlers": ["console"],
            "level": "DEBUG",
            "propagate": False,
        },
    },
}

사용

import logging
logger = logging.getLogger(__name__)

def my_view(request):
    logger.info("Processing request")
    logger.warning("Suspicious activity from %s", request.META["REMOTE_ADDR"])
    try:
        do_work()
    except Exception:
        logger.exception("Work failed")    # 자동 traceback 포함

logger.exceptionerror 레벨 + exc_info=True 자동.

Django 빌트인 logger

이름용도
django모든
django.request요청 (5xx 자동 ERROR)
django.serverrunserver
django.db.backendsSQL (DEBUG 시)
django.security.*보안 이벤트
django.template템플릿

JSON 구조화 로그

pip install python-json-logger
LOGGING["formatters"]["json"] = {
    "()": "pythonjsonlogger.json.JsonFormatter",
    "format": "%(asctime)s %(levelname)s %(name)s %(message)s",
    "rename_fields": {"levelname": "level", "asctime": "timestamp"},
}

LOGGING["handlers"]["console"]["formatter"] = "json"

출력:

{"timestamp": "2026-06-20 14:30:00", "level": "INFO", "name": "myapp.views", "message": "Processing", "user_id": 42}
logger.info("Processing", extra={"user_id": 42, "request_id": "abc123"})

extra는 자동으로 JSON에 병합.

Request ID 미들웨어

# middleware.py
import uuid
import contextvars

request_id_var = contextvars.ContextVar("request_id", default="-")

class RequestIDMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        req_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
        token = request_id_var.set(req_id)
        try:
            request.id = req_id
            response = self.get_response(request)
            response["X-Request-ID"] = req_id
            return response
        finally:
            request_id_var.reset(token)

# logging filter
class RequestIDFilter(logging.Filter):
    def filter(self, record):
        record.request_id = request_id_var.get()
        return True
# settings.py
LOGGING["filters"] = {
    "request_id": {"()": "myapp.middleware.RequestIDFilter"},
}
LOGGING["handlers"]["console"]["filters"] = ["request_id"]
LOGGING["formatters"]["verbose"]["format"] = "{asctime} [{request_id}] {levelname} {message}"

모든 로그에 자동 request ID. 분산 시스템에서 요청 추적 핵심.

Sentry 통합

pip install sentry-sdk
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn=env("SENTRY_DSN"),
    integrations=[DjangoIntegration()],
    traces_sample_rate=0.1,
    send_default_pii=False,
)

자동으로:

  • 모든 예외 (5xx) 캡처
  • 슬로우 트랜잭션 (performance)
  • 로깅의 ERROR 이상

CloudWatch / Datadog

watchtower (CloudWatch) 또는 datadog-agent + JSON 로그.

pip install watchtower
LOGGING["handlers"]["cloudwatch"] = {
    "class": "watchtower.CloudWatchLogHandler",
    "log_group": "myapp",
    "stream_name": "django",
}

structlog (대안)

pip install structlog

더 풍부한 구조화. 자동 컨텍스트 누적, JSON 또는 ConsoleRenderer, async 친화.

import structlog

logger = structlog.get_logger()
logger = logger.bind(user_id=42)
logger.info("processing", action="checkout")
logger = logger.bind(order_id=123)
logger.info("completed")

설정은 별도. Django와 통합 작업 필요.

자주 보는 패턴

사용자 컨텍스트

class UserContextMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.user.is_authenticated:
            sentry_sdk.set_user({"id": request.user.id, "email": request.user.email})
        return self.get_response(request)

SQL 로그 (개발)

LOGGING["loggers"]["django.db.backends"] = {
    "level": "DEBUG",
    "handlers": ["console"],
}

모든 SQL + 시간 출력. 프로덕션엔 끔 (성능).

Slow query 추적

class SlowQueryLogger:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        from django.db import connection
        n_before = len(connection.queries)
        response = self.get_response(request)
        if settings.DEBUG:
            for q in connection.queries[n_before:]:
                if float(q["time"]) > 0.1:
                    logger.warning("Slow query (%ss): %s", q["time"], q["sql"])
        return response

production은 APM이 자동.

비밀 필터링

SENSITIVE_FIELDS = {"password", "token", "api_key", "secret"}

class SensitiveDataFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, "args") and isinstance(record.args, dict):
            for key in SENSITIVE_FIELDS:
                if key in record.args:
                    record.args[key] = "[FILTERED]"
        return True

또는 Sentry의 before_send hook으로 자동 필터.

함정

1. console 백엔드 X (Docker)

Docker는 STDOUT만 capture. 파일에 쓰지 말고 console로:

LOGGING["handlers"] = {
    "console": {
        "class": "logging.StreamHandler",
    },
}

2. log level 너무 낮게

LOGGING["root"]["level"] = "DEBUG"    # 모든 로그 (디스크 폭발)

INFO 또는 WARNING이 표준. DEBUG는 개발만.

3. propagate=False 누락

LOGGING["loggers"]["myapp"] = {
    "handlers": ["console"],
}
# propagate=True (기본) → root logger에도 전달 → 중복

"propagate": False로 중복 방지.

4. exc_info 빠뜨림

logger.error("Failed: %s", e)        # exception 정보 X
logger.exception("Failed")           # stacktrace 포함
logger.error("Failed", exc_info=True)

5. 큰 객체 로깅

logger.info("user: %s", user)    # user.__str__()
logger.info("user data: %s", user.__dict__)   # 전체 dict 직렬화 (큼)

필요한 필드만.

모니터링 도구

도구특징
Sentry에러 + 일부 APM
Datadog종합
New Relic종합
ELK (Elasticsearch + Logstash + Kibana)자체 호스팅
Loki + Grafana자체 호스팅, 가벼움
CloudWatchAWS 통합

대부분 Django 자동 통합 라이브러리 제공.

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

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