Files
mostovik-backend/src/apps/core/middleware.py
Aleksandr Meshchriakov f121445313 feat(core): add core module with mixins, services, and background jobs
- Add Model Mixins: TimestampMixin, SoftDeleteMixin, AuditMixin, etc.
- Add Base Services: BaseService, BulkOperationsMixin, QueryOptimizerMixin
- Add Base ViewSets with bulk operations
- Add BackgroundJob model for Celery task tracking
- Add BaseAppCommand for management commands
- Add permissions, pagination, filters, cache, logging
- Migrate tests to factory_boy + faker
- Add CHANGELOG.md
- 297 tests passing
2026-01-21 11:47:26 +01:00

132 lines
3.8 KiB
Python

"""
Core middleware components.
Provides Request ID tracking and other cross-cutting concerns.
"""
import logging
import threading
import uuid
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger(__name__)
# Thread-local storage for request context
_request_context = threading.local()
def get_request_id() -> str | None:
"""Get current request ID from thread-local storage."""
return getattr(_request_context, "request_id", None)
def get_current_request():
"""Get current request from thread-local storage."""
return getattr(_request_context, "request", None)
class RequestIDMiddleware(MiddlewareMixin):
"""
Middleware that generates or extracts a unique request ID for each request.
The request ID is:
1. Extracted from X-Request-ID header if present
2. Generated as UUID4 if not present
3. Added to response headers
4. Available via get_request_id() for logging
Usage in logging:
from apps.core.middleware import get_request_id
logger.info(f"[{get_request_id()}] Processing request")
"""
REQUEST_ID_HEADER = "X-Request-ID"
def process_request(self, request):
"""Extract or generate request ID and store in thread-local."""
request_id = request.headers.get(self.REQUEST_ID_HEADER)
if not request_id:
request_id = str(uuid.uuid4())
request.request_id = request_id
_request_context.request_id = request_id
_request_context.request = request
return None
def process_response(self, request, response):
"""Add request ID to response headers."""
request_id = getattr(request, "request_id", None)
if request_id:
response[self.REQUEST_ID_HEADER] = request_id
# Clean up thread-local storage
_request_context.request_id = None
_request_context.request = None
return response
def process_exception(self, request, exception):
"""Log exception with request ID."""
request_id = getattr(request, "request_id", "unknown")
logger.error(f"[{request_id}] Unhandled exception: {exception}")
return None
class RequestLoggingMiddleware(MiddlewareMixin):
"""
Middleware for logging request/response details.
Logs:
- Request method, path, and request ID
- Response status code and timing (if available)
"""
def process_request(self, request):
"""Log incoming request."""
import time
request._start_time = time.time()
request_id = getattr(request, "request_id", "N/A")
logger.info(
f"[{request_id}] {request.method} {request.path} - Started",
extra={
"request_id": request_id,
"method": request.method,
"path": request.path,
"user": getattr(request.user, "id", None)
if hasattr(request, "user")
else None,
},
)
return None
def process_response(self, request, response):
"""Log response details."""
import time
duration = None
if hasattr(request, "_start_time"):
duration = time.time() - request._start_time
request_id = getattr(request, "request_id", "N/A")
logger.info(
f"[{request_id}] {request.method} {request.path} - "
f"{response.status_code} ({duration:.3f}s)"
if duration
else f"[{request_id}] {request.method} {request.path} - "
f"{response.status_code}",
extra={
"request_id": request_id,
"method": request.method,
"path": request.path,
"status_code": response.status_code,
"duration": duration,
},
)
return response