mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
"""
|
|
Debugging middleware for detailed request/response logging
|
|
"""
|
|
import json
|
|
import time
|
|
from datetime import datetime
|
|
from typing import Any, Dict, Optional
|
|
from fastapi import Request, Response
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from uuid import uuid4
|
|
|
|
from app.core.logging import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class DebuggingMiddleware(BaseHTTPMiddleware):
|
|
"""Middleware to log detailed request/response information for debugging"""
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
# Generate unique request ID for tracing
|
|
request_id = str(uuid4())
|
|
|
|
# Skip debugging for health checks and static files
|
|
if request.url.path in ["/health", "/docs", "/redoc", "/openapi.json"] or \
|
|
request.url.path.startswith("/static"):
|
|
return await call_next(request)
|
|
|
|
# Log request details
|
|
request_body = None
|
|
if request.method in ["POST", "PUT", "PATCH"]:
|
|
try:
|
|
# Clone request body to avoid consuming it
|
|
body_bytes = await request.body()
|
|
if body_bytes:
|
|
try:
|
|
request_body = json.loads(body_bytes)
|
|
except json.JSONDecodeError:
|
|
request_body = body_bytes.decode('utf-8', errors='replace')
|
|
# Restore body for downstream processing
|
|
request._body = body_bytes
|
|
except Exception:
|
|
request_body = "[Failed to read request body]"
|
|
|
|
# Extract headers we care about
|
|
headers_to_log = {
|
|
"authorization": request.headers.get("Authorization", "")[:50] + "..." if
|
|
request.headers.get("Authorization") else None,
|
|
"content-type": request.headers.get("Content-Type"),
|
|
"user-agent": request.headers.get("User-Agent"),
|
|
"x-forwarded-for": request.headers.get("X-Forwarded-For"),
|
|
"x-real-ip": request.headers.get("X-Real-IP"),
|
|
}
|
|
|
|
# Log request
|
|
logger.info("=== API REQUEST DEBUG ===", extra={
|
|
"request_id": request_id,
|
|
"method": request.method,
|
|
"url": str(request.url),
|
|
"path": request.url.path,
|
|
"query_params": dict(request.query_params),
|
|
"headers": {k: v for k, v in headers_to_log.items() if v is not None},
|
|
"body": request_body,
|
|
"client_ip": request.client.host if request.client else None,
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
})
|
|
|
|
# Process the request
|
|
start_time = time.time()
|
|
response = None
|
|
response_body = None
|
|
|
|
try:
|
|
response = await call_next(request)
|
|
|
|
# Capture response body for successful JSON responses
|
|
if response.status_code < 400 and isinstance(response, JSONResponse):
|
|
try:
|
|
response_body = json.loads(response.body.decode('utf-8'))
|
|
except:
|
|
response_body = "[Failed to decode response body]"
|
|
|
|
except Exception as e:
|
|
logger.error(f"Request processing failed: {str(e)}", extra={
|
|
"request_id": request_id,
|
|
"error": str(e),
|
|
"error_type": type(e).__name__
|
|
})
|
|
response = JSONResponse(
|
|
status_code=500,
|
|
content={"error": "INTERNAL_ERROR", "message": "Internal server error"}
|
|
)
|
|
|
|
# Calculate timing
|
|
end_time = time.time()
|
|
duration = (end_time - start_time) * 1000 # milliseconds
|
|
|
|
# Log response
|
|
logger.info("=== API RESPONSE DEBUG ===", extra={
|
|
"request_id": request_id,
|
|
"status_code": response.status_code,
|
|
"duration_ms": round(duration, 2),
|
|
"response_body": response_body,
|
|
"response_headers": dict(response.headers),
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
})
|
|
|
|
return response
|
|
|
|
|
|
def setup_debugging_middleware(app):
|
|
"""Add debugging middleware to the FastAPI app"""
|
|
app.add_middleware(DebuggingMiddleware)
|
|
logger.info("Debugging middleware configured") |