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>
This commit is contained in:
118
backend/app/core/middleware.py
Normal file
118
backend/app/core/middleware.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user