Files
resolutionflow/backend/app/core/middleware.py
Michael Chihlas 7d96807fb1 Add production logging, datetime fixes, and session tests
DateTime Timezone Handling (Critical Bug Fix):
- Updated all models to use DateTime(timezone=True) for PostgreSQL
- Changed datetime defaults to lambda: datetime.now(timezone.utc)
- Fixed mixing of timezone-aware and timezone-naive datetime objects
- Resolved Internal Server Errors in session completion endpoint
- Affected models: User, Team, Tree, Session, Attachment

Production Logging System:
- Created logging_config.py with structured logging setup
- Added log rotation (10MB files, 10 backups) for production
- Implemented RequestLoggingMiddleware with correlation IDs
- Added ErrorLoggingMiddleware for comprehensive error tracking
- Integrated logging into main.py application startup
- Supports dev/prod modes with appropriate log levels

Integration Tests - Session Workflow:
- Created test_sessions.py with 12 comprehensive tests
- Session lifecycle: create, update, complete
- Session export in multiple formats (markdown, text, HTML)
- Error handling and authorization checks
- Added pytest.ini with coverage configuration
- Added requirements-dev.txt with pytest dependencies

Following 2026 FastAPI best practices for timezone handling and structured logging.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-27 20:39:09 -05:00

119 lines
3.5 KiB
Python

"""
Middleware for request logging and tracking.
Implements correlation ID tracking for requests and comprehensive logging
following 2026 FastAPI best practices.
"""
import logging
import time
import uuid
from typing import Callable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp
logger = logging.getLogger(__name__)
class RequestLoggingMiddleware(BaseHTTPMiddleware):
"""
Middleware to log all HTTP requests with timing and correlation IDs.
Features:
- Generates unique correlation ID for each request
- Logs request method, path, and client IP
- Measures and logs request processing time
- Logs response status code
- Adds correlation ID to response headers for tracing
"""
async def dispatch(
self, request: Request, call_next: Callable
) -> Response:
# Generate correlation ID for request tracking
correlation_id = str(uuid.uuid4())
request.state.correlation_id = correlation_id
# Get client IP
client_host = request.client.host if request.client else "unknown"
# Log incoming request
logger.info(
f"Request started - "
f"method={request.method} "
f"path={request.url.path} "
f"client={client_host} "
f"correlation_id={correlation_id}"
)
# Process request and measure time
start_time = time.time()
try:
response = await call_next(request)
process_time = time.time() - start_time
# Add correlation ID to response headers
response.headers["X-Correlation-ID"] = correlation_id
response.headers["X-Process-Time"] = str(process_time)
# Log response
logger.info(
f"Request completed - "
f"method={request.method} "
f"path={request.url.path} "
f"status={response.status_code} "
f"duration={process_time:.3f}s "
f"correlation_id={correlation_id}"
)
return response
except Exception as exc:
process_time = time.time() - start_time
# Log error
logger.error(
f"Request failed - "
f"method={request.method} "
f"path={request.url.path} "
f"duration={process_time:.3f}s "
f"correlation_id={correlation_id} "
f"error={str(exc)}",
exc_info=True
)
# Re-raise exception to be handled by FastAPI
raise
class ErrorLoggingMiddleware(BaseHTTPMiddleware):
"""
Middleware to catch and log unhandled exceptions.
Ensures all exceptions are logged before being returned to the client,
providing full stack traces for debugging.
"""
async def dispatch(
self, request: Request, call_next: Callable
) -> Response:
try:
response = await call_next(request)
return response
except Exception as exc:
correlation_id = getattr(request.state, "correlation_id", "unknown")
logger.error(
f"Unhandled exception - "
f"method={request.method} "
f"path={request.url.path} "
f"correlation_id={correlation_id}",
exc_info=True
)
# Re-raise to let FastAPI handle the response
raise