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
This commit is contained in:
131
src/apps/core/middleware.py
Normal file
131
src/apps/core/middleware.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user