|
2 | 2 | Request logging middleware that captures comprehensive data from all HTTP requests. |
3 | 3 | """ |
4 | 4 |
|
5 | | -import logging |
6 | 5 | import time |
7 | 6 | import uuid |
8 | 7 | from typing import Any |
9 | 8 |
|
| 9 | +from delphi_utils import get_structured_logger |
10 | 10 | from django.utils.deprecation import MiddlewareMixin |
11 | 11 |
|
12 | 12 | from epiportal.utils import get_client_ip |
13 | 13 |
|
14 | | -logger = logging.getLogger("epiportal.requests") |
| 14 | +logger = get_structured_logger("epiportal.requests") |
15 | 15 |
|
16 | 16 | # Headers that may contain sensitive data - values will be redacted |
17 | 17 | SENSITIVE_HEADERS = frozenset( |
|
27 | 27 | ) |
28 | 28 | ) |
29 | 29 |
|
30 | | -# Maximum bytes of request/response body to log (prevents log bloat) |
31 | | -MAX_BODY_LOG_SIZE = 4096 |
32 | | - |
33 | 30 | # Path segments to exclude from request logging (matched anywhere in path) |
34 | 31 | LOG_EXCLUDE_PATH_PATTERNS = ( |
35 | 32 | # "get_table_stats_info", |
@@ -57,19 +54,6 @@ def _sanitize_headers(meta: dict) -> dict[str, str]: |
57 | 54 | return headers |
58 | 55 |
|
59 | 56 |
|
60 | | -def _safe_body_preview(body: bytes | None, max_size: int = MAX_BODY_LOG_SIZE) -> str | None: |
61 | | - """Return a safe preview of request/response body, truncated if large.""" |
62 | | - if body is None or len(body) == 0: |
63 | | - return None |
64 | | - try: |
65 | | - decoded = body.decode("utf-8", errors="replace") |
66 | | - if len(decoded) > max_size: |
67 | | - return decoded[:max_size] + f"... [truncated, total {len(body)} bytes]" |
68 | | - return decoded |
69 | | - except Exception: |
70 | | - return f"[binary, {len(body)} bytes]" |
71 | | - |
72 | | - |
73 | 57 | class RequestLoggingMiddleware(MiddlewareMixin): |
74 | 58 | """ |
75 | 59 | Middleware that logs every HTTP request with comprehensive request and response data. |
@@ -120,31 +104,11 @@ def process_response(self, request, response): |
120 | 104 | else: |
121 | 105 | log_data["user"] = "anonymous" |
122 | 106 |
|
123 | | - # Request body (for methods that typically have one) |
124 | | - if request.method in ("POST", "PUT", "PATCH"): |
125 | | - try: |
126 | | - body = getattr(request, "_body", None) or request.body |
127 | | - log_data["request_body_preview"] = _safe_body_preview(body) |
128 | | - except Exception as e: |
129 | | - log_data["request_body_error"] = str(e) |
130 | | - |
131 | | - # Response body (for API responses, avoid huge HTML) |
132 | | - if ( |
133 | | - "application/json" in (response.get("Content-Type") or "") |
134 | | - and hasattr(response, "content") |
135 | | - ): |
136 | | - log_data["response_body_preview"] = _safe_body_preview(response.content) |
137 | | - |
138 | 107 | if duration_ms is not None: |
139 | 108 | log_data["duration_ms"] = round(duration_ms, 2) |
140 | 109 |
|
141 | | - logger.info( |
142 | | - "%s %s %s", |
143 | | - request.method, |
144 | | - request.path, |
145 | | - response.status_code, |
146 | | - extra=log_data, |
147 | | - ) |
| 110 | + log_data["message"] = f"{request.method} {request.path} {response.status_code}" |
| 111 | + logger.info("request", **log_data) |
148 | 112 |
|
149 | 113 | except Exception as e: |
150 | 114 | logger.exception("Error in request logging middleware: %s", e) |
|
0 commit comments