# Django 미들웨어 완벽 가이드 2026: 커스텀 미들웨어, 로깅 구현 및 기술 면접 대비 > Django 6.0 미들웨어를 심층 분석합니다. 커스텀 미들웨어 작성법, 비동기 미들웨어, 요청 ID 로깅, 그리고 기술 면접에서 자주 나오는 질문과 답변을 상세히 다룹니다. - Published: 2026-09-18 - Updated: 2026-09-18 - Author: Anthony Fillion-Maillet - Tags: django, middleware, python, logging, interview - Reading time: 12 min --- Django 미들웨어는 모든 요청-응답 사이클의 핵심을 담당하지만, 많은 개발자들이 미들웨어를 블랙박스처럼 취급합니다. 미들웨어의 실행 순서, 커스텀 미들웨어 작성 시점, 미들웨어 레벨에서의 로깅 구현 방법을 이해하는 것은 기술 면접에서 시니어 Django 개발자와 주니어 개발자를 구분하는 중요한 기준이 됩니다. > **Django 6.0 미들웨어** > > Django 6.0은 네이티브 async/await 구문을 사용한 비동기 미들웨어를 완벽하게 지원합니다. 미들웨어 클래스에 `async_capable = True`와 `sync_capable = False`를 설정하면 스레드 오버헤드 없이 요청을 처리할 수 있습니다. ## Django 미들웨어의 요청-응답 사이클 실행 순서 Django의 미들웨어는 "양파(onion) 모델"을 따릅니다. 요청이 도착하면 `MIDDLEWARE` 목록의 위에서 아래로 각 미들웨어를 통과합니다. 뷰가 응답을 반환하면, 응답은 아래에서 위로 미들웨어를 역순으로 통과합니다. 이 순서는 중요합니다. 세션 데이터가 필요한 미들웨어는 `SessionMiddleware` 이후에 실행되어야 합니다. Django 6.0의 기본 `MIDDLEWARE` 설정은 다음과 같습니다: ```python # settings.py MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ``` 각 미들웨어는 `get_response()`를 호출하는 대신 `HttpResponse`를 직접 반환하여 체인을 쇼트서킷(단락)할 수 있습니다. 이 경우 내부 미들웨어와 뷰는 실행되지 않지만, 외부 미들웨어는 여전히 응답을 처리합니다. ## Django 6.0에서 클래스 기반 커스텀 미들웨어 작성하기 현대적인 미들웨어 패턴은 `__init__`과 `__call__` 메서드를 가진 클래스를 필요로 합니다. `__init__` 메서드는 서버 시작 시 한 번만 실행되고, `__call__`은 모든 요청에 대해 실행됩니다. ```python # middleware/timing.py import time import logging logger = logging.getLogger(__name__) class RequestTimingMiddleware: """Logs the time taken to process each request.""" def __init__(self, get_response): # Called once when the server starts self.get_response = get_response def __call__(self, request): # Code before the view executes start_time = time.perf_counter() # Call the next middleware or view response = self.get_response(request) # Code after the view executes duration_ms = (time.perf_counter() - start_time) * 1000 logger.info( "Request %s %s completed in %.2fms", request.method, request.path, duration_ms ) return response ``` 미들웨어를 등록하려면 `settings.py`의 `MIDDLEWARE`에 임포트 경로를 추가합니다. 위치가 중요합니다. 타이밍 미들웨어는 전체 요청 라이프사이클을 측정하기 위해 목록의 앞쪽에 배치해야 합니다. ## 고동시성 Django 애플리케이션을 위한 비동기 미들웨어 Django 6.0은 비동기 요청을 네이티브로 처리합니다. ASGI에서 비동기 뷰를 실행하는 애플리케이션의 경우, 동기 미들웨어는 Django가 동기 미들웨어를 스레드 풀 실행기로 래핑하기 때문에 성능 병목현상을 일으킵니다. 비동기 미들웨어를 작성하면 이 오버헤드를 제거할 수 있습니다. ```python # middleware/async_timing.py import time import logging from asgiref.sync import iscoroutinefunction, markcoroutinefunction logger = logging.getLogger(__name__) class AsyncRequestTimingMiddleware: """Async-native timing middleware for ASGI deployments.""" async_capable = True sync_capable = False def __init__(self, get_response): self.get_response = get_response if iscoroutinefunction(self.get_response): markcoroutinefunction(self) async def __call__(self, request): start_time = time.perf_counter() # Await the next middleware or async view response = await self.get_response(request) duration_ms = (time.perf_counter() - start_time) * 1000 logger.info( "Async request %s %s completed in %.2fms", request.method, request.path, duration_ms ) return response ``` `django.utils.decorators`의 `@sync_and_async_middleware` 데코레이터는 런타임에 `iscoroutinefunction(get_response)`를 확인하여 동기 및 비동기 요청을 모두 처리하는 미들웨어를 생성합니다. ## 요청 ID 로깅 미들웨어 구현 분산 시스템 간에 로그를 상호 연관시키려면 요청별 고유 식별자가 필요합니다. 이 패턴은 미들웨어, 로깅, 프로덕션 시스템 디버깅에 대한 이해를 보여주기 때문에 [Django 면접 질문](/technologies/django/interview-questions/django-middleware)에서 자주 등장합니다. ```python # middleware/request_id.py import uuid import logging class RequestIDMiddleware: """Attaches a unique request ID to every request for log correlation.""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # Generate or extract request ID from header request_id = request.headers.get("X-Request-ID") if not request_id: request_id = str(uuid.uuid4()) # Attach to request object for access in views request.request_id = request_id response = self.get_response(request) # Add request ID to response headers response["X-Request-ID"] = request_id return response ``` 모든 로그 메시지에 `request_id`를 포함하려면 커스텀 로깅 필터를 생성합니다: ```python # logging_filters.py import logging class RequestIDFilter(logging.Filter): """Injects request_id into log records.""" def filter(self, record): from django.middleware.request_id import local record.request_id = getattr(local, "request_id", "no-request-id") return True ``` `settings.py`에서 필터를 구성합니다: ```python # settings.py LOGGING = { "version": 1, "disable_existing_loggers": False, "filters": { "request_id": { "()": "logging_filters.RequestIDFilter", }, }, "formatters": { "verbose": { "format": "[{levelname}] {asctime} [{request_id}] {name}: {message}", "style": "{", }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "verbose", "filters": ["request_id"], }, }, "root": { "handlers": ["console"], "level": "INFO", }, } ``` ## 세밀한 제어를 위한 미들웨어 훅 메서드 `__call__` 외에도 Django 미들웨어는 세 가지 훅 메서드를 지원합니다: `process_view`, `process_exception`, `process_template_response`. 이들은 요청 라이프사이클의 특정 지점에서 제어를 제공합니다. ### 실행 전 로직을 위한 process_view ```python # middleware/permission_check.py from django.http import HttpResponseForbidden class PermissionCheckMiddleware: """Checks permissions before view execution.""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): # Access view metadata required_permission = getattr(view_func, "required_permission", None) if required_permission: if not request.user.has_perm(required_permission): return HttpResponseForbidden("Permission denied") # Return None to continue to the view return None ``` 이 패턴을 사용하면 뷰에 권한 요구사항을 데코레이터로 지정할 수 있습니다: ```python # views.py def require_permission(perm): def decorator(view_func): view_func.required_permission = perm return view_func return decorator @require_permission("app.can_edit") def edit_resource(request, pk): # View logic pass ``` ### 중앙집중식 에러 핸들링을 위한 process_exception ```python # middleware/error_tracking.py import logging import traceback logger = logging.getLogger(__name__) class ErrorTrackingMiddleware: """Logs exceptions with full context before Django handles them.""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): return self.get_response(request) def process_exception(self, request, exception): logger.error( "Unhandled exception in %s %s: %s\n%s", request.method, request.path, str(exception), traceback.format_exc(), extra={ "user_id": getattr(request.user, "id", None), "request_id": getattr(request, "request_id", None), } ) # Return None to let Django handle the exception return None ``` 미들웨어의 `process_exception` 메서드는 역순으로 실행됩니다. 하나가 `HttpResponse`를 반환하면, 목록의 앞쪽에 있는 미들웨어는 예외를 볼 수 없습니다. ## Django 6.0 보안 미들웨어 업데이트 Django 6.0에는 `UpdateCacheMiddleware`에 대한 여러 보안 수정이 포함되어 있습니다. [2026년 Django 보안 릴리스](https://www.djangoproject.com/weblog/2026/jun/03/security-releases/)에 따르면, 대소문자가 혼합된 `Cache-Control: private`가 있는 응답이 잘못 캐시되었고, `Authorization` 헤더가 있는 요청에 대한 응답이 공유 캐시에 저장될 수 있었습니다. Django 5.1에서 도입된 LoginRequiredMiddleware는 사이트 전체에 인증을 강제합니다: ```python # settings.py MIDDLEWARE = [ # ... other middleware "django.contrib.auth.middleware.LoginRequiredMiddleware", ] # Exempt specific views from django.contrib.auth.decorators import login_not_required @login_not_required def public_view(request): return HttpResponse("Public content") ``` 이것은 보호된 뷰에 `@login_required`를 데코레이팅하는 전통적인 패턴을 뒤집습니다. 대부분의 뷰에서 인증이 필요한 애플리케이션의 경우, 보일러플레이트를 줄이고 보호된 엔드포인트가 의도치 않게 노출되는 것을 방지합니다. ## MiddlewareMixin과 레거시 미들웨어에서의 마이그레이션 `MiddlewareMixin`은 구식 미들웨어(`process_request`와 `process_response` 메서드 사용)를 새로운 callable 패턴으로 연결합니다. `django.utils.deprecation.MiddlewareMixin`은 deprecated되었으며 Django 6.2에서 제거될 예정입니다. 대신 `django.middleware.MiddlewareMixin`을 사용하십시오. ```python # middleware/legacy_style.py from django.middleware import MiddlewareMixin class LegacyStyleMiddleware(MiddlewareMixin): """Middleware using the legacy hook-based pattern.""" def process_request(self, request): # Called before view, return None to continue request.custom_attribute = "value" return None def process_response(self, request, response): # Called after view, must return response response["X-Custom-Header"] = "middleware-added" return response ``` 새로운 미들웨어에서는 `MiddlewareMixin`을 피하는 것이 좋습니다. callable 패턴은 더 명확한 제어 흐름을 제공하고 비동기 Django와의 호환성도 향상됩니다. ## Django 미들웨어 기술 면접에서 자주 나오는 질문 기술 면접에서는 Django의 요청 라이프사이클에 대한 이해를 평가하기 위해 미들웨어 질문이 자주 포함됩니다. 이러한 질문은 [시니어 Django 개발자 면접](/blog/django/django-interview-orm-middleware-drf)에서 자주 등장합니다. **Q: 미들웨어 순서와 응답의 실행 순서 차이점은 무엇입니까?** 미들웨어는 `MIDDLEWARE`를 통해 요청을 위에서 아래로 처리하지만, 응답은 아래에서 위로 처리합니다. 양파처럼 생각하면 됩니다. 가장 바깥쪽 미들웨어가 요청을 먼저 보고, 응답을 마지막에 봅니다. **Q: 미들웨어 쇼트서킷은 어떻게 작동합니까?** 미들웨어가 `get_response()`를 호출하는 대신 `HttpResponse`를 반환하면, 요청은 내부 미들웨어나 뷰에 도달하지 않습니다. 응답은 돌아가는 길에 모든 외부 미들웨어를 통과합니다. 이 동작은 쇼트서킷과 관계없이 모든 `process_response` 메서드가 실행되었던 레거시 `MIDDLEWARE_CLASSES` 설정과 다릅니다. **Q: 미들웨어에서 `request.user`에 언제 접근해야 합니까?** `AuthenticationMiddleware`가 실행된 후에만 가능합니다. 인증 미들웨어가 연결하기 전에 `request.user`에 접근하면 `AttributeError`가 발생합니다. 사용자 정보가 필요한 커스텀 미들웨어는 `MIDDLEWARE` 목록에서 `AuthenticationMiddleware` 이후에 배치해야 합니다. **Q: 미들웨어를 비동기 호환으로 만들려면 어떻게 해야 합니까?** 미들웨어 클래스에 `async_capable = True`를 설정하고 `await get_response(request)`를 사용하여 `__call__`을 비동기 메서드로 구현합니다. 동기 및 비동기 컨텍스트 모두에서 작동해야 하는 미들웨어의 경우 `@sync_and_async_middleware`를 사용하고 `iscoroutinefunction(get_response)`를 확인하여 적절히 분기합니다. ## 프로덕션 환경과 면접을 위한 Django 미들웨어 마스터하기 Django 미들웨어는 전체 요청 라이프사이클에 걸친 횡단 관심사를 제어합니다. Django 6.0에서 미들웨어를 다룰 때 핵심 포인트는 다음과 같습니다: - 미들웨어는 양파 패턴으로 실행됨: 요청은 위에서 아래로, 응답은 아래에서 위로 흐름 - ASGI 배포에서 스레드 풀 오버헤드를 피하려면 `async_capable = True` 설정 - `process_view`는 실행 전 뷰 메타데이터 검사에, `process_exception`은 중앙집중식 에러 로깅에 사용 - 요청 ID 미들웨어는 분산 서비스 간 로그 상관관계를 가능하게 하며 프로덕션의 일반적인 요구사항 - `LoginRequiredMiddleware`는 인증 패턴을 뒤집음: 뷰는 기본적으로 보호되고, 공개 뷰는 명시적으로 면제됨 - deprecated된 `django.utils.deprecation.MiddlewareMixin`은 Django 6.2에서 `django.middleware.MiddlewareMixin`으로 이동 - 미들웨어 순서는 어떤 컴포넌트가 `request.user`, 세션 데이터 및 기타 요청 속성을 볼 수 있는지 결정 Django ORM과 REST framework 패턴에 대한 더 자세한 내용은 [Django ORM 쿼리 최적화 가이드](/blog/django/django-orm-optimizing-queries)를 참조하세요. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/django/django-advanced-middleware-logging-2026