depscreen / app /middleware /request_logging.py
halsabbah's picture
Initial commit β€” DepScreen backend
dbc1e7d
"""
Request logging middleware.
Logs every request with: method, path, status code, duration, user ID (if authenticated).
Uses structured JSON format for production observability.
"""
import logging
import time
from uuid import uuid4
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
logger = logging.getLogger("depscreen.requests")
class RequestLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
request_id = request.headers.get("X-Request-ID", str(uuid4())[:8])
start_time = time.time()
# Extract user ID from JWT if present (without full auth β€” just peek)
user_id = "anonymous"
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer ") and len(auth_header) > 20:
user_id = "authenticated" # Don't decode JWT here β€” just note it exists
response = await call_next(request)
duration_ms = round((time.time() - start_time) * 1000, 1)
status = response.status_code
# Skip health checks and static assets from logs
path = request.url.path
if path in ("/", "/health", "/health/live", "/health/ready", "/favicon.ico"):
return response
log_data = {
"request_id": request_id,
"method": request.method,
"path": path,
"status": status,
"duration_ms": duration_ms,
"user": user_id,
"ip": request.client.host if request.client else "unknown",
}
if status >= 500:
logger.error(f"[{request_id}] {request.method} {path} β†’ {status} ({duration_ms}ms)", extra=log_data)
elif status >= 400:
logger.warning(f"[{request_id}] {request.method} {path} β†’ {status} ({duration_ms}ms)", extra=log_data)
else:
logger.info(f"[{request_id}] {request.method} {path} β†’ {status} ({duration_ms}ms)", extra=log_data)
return response