1+ from contextlib import asynccontextmanager
12import logging
23import sys
34
4- from fastapi import Depends , FastAPI
5+ from fastapi import Depends , FastAPI , Request , status
6+ from fastapi .exceptions import RequestValidationError
57from fastapi .middleware .cors import CORSMiddleware
68from fastapi .openapi .docs import get_swagger_ui_html
79from fastapi .openapi .utils import get_openapi
810from fastapi .responses import JSONResponse
11+ from sqlalchemy import text
12+ from starlette .middleware .base import BaseHTTPMiddleware
913
1014from app .api import auth , donate , github , roadmap , waitlist
1115from app .core .auth import ClerkAuthMiddleware , ClerkClaims , require_clerk_auth
1216from app .core .config import settings
17+ from app .core .database import SessionLocal
1318
1419
1520def _configure_logging () -> None :
@@ -28,6 +33,26 @@ def _configure_logging() -> None:
2833
2934
3035_configure_logging ()
36+ logger = logging .getLogger (__name__ )
37+
38+
39+ @asynccontextmanager
40+ async def lifespan (app : FastAPI ):
41+ """Handle application startup and shutdown."""
42+ # Startup: Test database connection
43+ logger .info ("Testing database connection..." )
44+ try :
45+ with SessionLocal () as session :
46+ session .execute (text ("SELECT 1" ))
47+ session .commit ()
48+ logger .info ("Database connection successful" )
49+ except Exception as e :
50+ logger .error (f"Database connection failed: { e } " , exc_info = True )
51+ # Don't fail startup, but log the error
52+ yield
53+ # Shutdown
54+ logger .info ("Application shutting down" )
55+
3156
3257app = FastAPI (
3358 title = settings .project_name ,
@@ -36,6 +61,7 @@ def _configure_logging() -> None:
3661 docs_url = None ,
3762 redoc_url = None ,
3863 openapi_url = None ,
64+ lifespan = lifespan ,
3965)
4066
4167# Add CORS middleware
@@ -63,6 +89,32 @@ def _configure_logging() -> None:
6389)
6490app .add_middleware (ClerkAuthMiddleware )
6591
92+
93+ class RequestLoggingMiddleware (BaseHTTPMiddleware ):
94+ """Log all incoming requests for debugging."""
95+
96+ async def dispatch (self , request : Request , call_next ):
97+ logger .info (
98+ f"Request: { request .method } { request .url .path } "
99+ f"from { request .client .host if request .client else 'unknown' } "
100+ )
101+ try :
102+ response = await call_next (request )
103+ logger .info (
104+ f"Response: { request .method } { request .url .path } "
105+ f"-> { response .status_code } "
106+ )
107+ return response
108+ except Exception as e :
109+ logger .error (
110+ f"Error processing { request .method } { request .url .path } : { e } " ,
111+ exc_info = True ,
112+ )
113+ raise
114+
115+
116+ app .add_middleware (RequestLoggingMiddleware )
117+
66118# Include routers
67119protected = [Depends (require_clerk_auth )]
68120
@@ -117,3 +169,32 @@ async def openapi_json(_: ClerkClaims = Depends(require_clerk_auth)):
117169 routes = app .routes ,
118170 )
119171 return JSONResponse (schema )
172+
173+
174+ # Global exception handlers
175+ @app .exception_handler (Exception )
176+ async def global_exception_handler (request : Request , exc : Exception ):
177+ """Catch all unhandled exceptions and log them."""
178+ logger .error (
179+ f"Unhandled exception: { request .method } { request .url .path } " ,
180+ exc_info = True ,
181+ )
182+ return JSONResponse (
183+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
184+ content = {
185+ "detail" : "Internal server error" ,
186+ "path" : str (request .url .path ),
187+ },
188+ )
189+
190+
191+ @app .exception_handler (RequestValidationError )
192+ async def validation_exception_handler (request : Request , exc : RequestValidationError ):
193+ """Handle request validation errors."""
194+ logger .warning (
195+ f"Validation error: { request .method } { request .url .path } - { exc .errors ()} "
196+ )
197+ return JSONResponse (
198+ status_code = status .HTTP_422_UNPROCESSABLE_ENTITY ,
199+ content = {"detail" : exc .errors (), "body" : exc .body },
200+ )
0 commit comments