|
|
""" |
|
|
Module: api.middleware.logging_middleware |
|
|
Description: Logging middleware for API request/response tracking |
|
|
Author: Anderson H. Silva |
|
|
Date: 2025-01-24 |
|
|
License: Proprietary - All rights reserved |
|
|
""" |
|
|
|
|
|
import time |
|
|
import uuid |
|
|
from typing import Callable |
|
|
|
|
|
from fastapi import Request, Response |
|
|
from starlette.middleware.base import BaseHTTPMiddleware |
|
|
|
|
|
from src.core import get_logger |
|
|
|
|
|
|
|
|
class LoggingMiddleware(BaseHTTPMiddleware): |
|
|
"""Middleware for logging API requests and responses.""" |
|
|
|
|
|
def __init__(self, app): |
|
|
"""Initialize logging middleware.""" |
|
|
super().__init__(app) |
|
|
self.logger = get_logger(__name__) |
|
|
|
|
|
async def dispatch(self, request: Request, call_next: Callable) -> Response: |
|
|
"""Process request with comprehensive logging.""" |
|
|
|
|
|
request_id = str(uuid.uuid4()) |
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
client_ip = self._get_client_ip(request) |
|
|
user_agent = request.headers.get("User-Agent", "Unknown") |
|
|
content_length = request.headers.get("Content-Length", "0") |
|
|
|
|
|
|
|
|
self.logger.info( |
|
|
"api_request_started", |
|
|
request_id=request_id, |
|
|
method=request.method, |
|
|
url=str(request.url), |
|
|
path=request.url.path, |
|
|
query_params=dict(request.query_params), |
|
|
client_ip=client_ip, |
|
|
user_agent=user_agent, |
|
|
content_length=content_length, |
|
|
) |
|
|
|
|
|
|
|
|
request.state.request_id = request_id |
|
|
|
|
|
try: |
|
|
|
|
|
response = await call_next(request) |
|
|
|
|
|
|
|
|
process_time = time.time() - start_time |
|
|
|
|
|
|
|
|
self.logger.info( |
|
|
"api_request_completed", |
|
|
request_id=request_id, |
|
|
method=request.method, |
|
|
path=request.url.path, |
|
|
status_code=response.status_code, |
|
|
process_time=process_time, |
|
|
response_size=response.headers.get("Content-Length", "unknown"), |
|
|
client_ip=client_ip, |
|
|
) |
|
|
|
|
|
|
|
|
response.headers["X-Request-ID"] = request_id |
|
|
response.headers["X-Process-Time"] = f"{process_time:.4f}" |
|
|
|
|
|
return response |
|
|
|
|
|
except Exception as exc: |
|
|
|
|
|
process_time = time.time() - start_time |
|
|
|
|
|
|
|
|
self.logger.error( |
|
|
"api_request_failed", |
|
|
request_id=request_id, |
|
|
method=request.method, |
|
|
path=request.url.path, |
|
|
error_type=type(exc).__name__, |
|
|
error_message=str(exc), |
|
|
process_time=process_time, |
|
|
client_ip=client_ip, |
|
|
) |
|
|
|
|
|
|
|
|
raise exc |
|
|
|
|
|
def _get_client_ip(self, request: Request) -> str: |
|
|
"""Extract client IP address from request.""" |
|
|
|
|
|
forwarded_for = request.headers.get("X-Forwarded-For") |
|
|
if forwarded_for: |
|
|
|
|
|
return forwarded_for.split(",")[0].strip() |
|
|
|
|
|
real_ip = request.headers.get("X-Real-IP") |
|
|
if real_ip: |
|
|
return real_ip |
|
|
|
|
|
|
|
|
return request.client.host if request.client else "unknown" |
|
|
|
|
|
def _should_log_body(self, request: Request) -> bool: |
|
|
"""Determine if request body should be logged.""" |
|
|
|
|
|
content_type = request.headers.get("Content-Type", "") |
|
|
content_length = int(request.headers.get("Content-Length", "0")) |
|
|
|
|
|
|
|
|
if content_length > 10240: |
|
|
return False |
|
|
|
|
|
|
|
|
if any(ct in content_type.lower() for ct in ["multipart/", "application/octet-stream", "image/", "video/", "audio/"]): |
|
|
return False |
|
|
|
|
|
return True |
|
|
|
|
|
def _sanitize_headers(self, headers: dict) -> dict: |
|
|
"""Remove sensitive information from headers.""" |
|
|
sensitive_headers = { |
|
|
"authorization", "x-api-key", "cookie", "set-cookie", |
|
|
"x-auth-token", "x-access-token", "x-csrf-token" |
|
|
} |
|
|
|
|
|
return { |
|
|
key: "***REDACTED***" if key.lower() in sensitive_headers else value |
|
|
for key, value in headers.items() |
|
|
} |