[Django] Logging: LOGGING 설정과 구조화 로그
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.exception은 error 레벨 + exc_info=True 자동.
Django 빌트인 logger
| 이름 | 용도 |
|---|---|
django | 모든 |
django.request | 요청 (5xx 자동 ERROR) |
django.server | runserver |
django.db.backends | SQL (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 | 자체 호스팅, 가벼움 |
| CloudWatch | AWS 통합 |
대부분 Django 자동 통합 라이브러리 제공.
💬 댓글