Skip to content

Commit c86ee89

Browse files
committed
feat(backend): enhance application logging and error handling
- Implement request logging middleware to log incoming requests and responses. - Add global exception handlers for unhandled exceptions and request validation errors. - Introduce application lifespan management to test database connection on startup and log shutdown events. - Improve logging for better debugging and error tracking.
1 parent 7fe8b3c commit c86ee89

File tree

1 file changed

+82
-1
lines changed

1 file changed

+82
-1
lines changed

commitly-backend/app/main.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
from contextlib import asynccontextmanager
12
import logging
23
import sys
34

4-
from fastapi import Depends, FastAPI
5+
from fastapi import Depends, FastAPI, Request, status
6+
from fastapi.exceptions import RequestValidationError
57
from fastapi.middleware.cors import CORSMiddleware
68
from fastapi.openapi.docs import get_swagger_ui_html
79
from fastapi.openapi.utils import get_openapi
810
from fastapi.responses import JSONResponse
11+
from sqlalchemy import text
12+
from starlette.middleware.base import BaseHTTPMiddleware
913

1014
from app.api import auth, donate, github, roadmap, waitlist
1115
from app.core.auth import ClerkAuthMiddleware, ClerkClaims, require_clerk_auth
1216
from app.core.config import settings
17+
from app.core.database import SessionLocal
1318

1419

1520
def _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

3257
app = 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
)
6490
app.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
67119
protected = [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

Comments
 (0)