|
|
|
|
@ -96,58 +96,86 @@ def handle_exception(request: Request, exc: Exception):
|
|
|
|
|
"""
|
|
|
|
|
Global exception handler for Fastapi application.
|
|
|
|
|
"""
|
|
|
|
|
import uuid
|
|
|
|
|
error_id = str(uuid.uuid1())
|
|
|
|
|
request_info = get_request_context(request)
|
|
|
|
|
|
|
|
|
|
# Store error_id in request.state for middleware/logging
|
|
|
|
|
request.state.error_id = error_id
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, RateLimitExceeded):
|
|
|
|
|
return _rate_limit_exceeded_handler(request, exc)
|
|
|
|
|
logging.warning(
|
|
|
|
|
f"Rate limit exceeded | Error ID: {error_id}",
|
|
|
|
|
extra={
|
|
|
|
|
"error_id": error_id,
|
|
|
|
|
"error_category": "rate_limit",
|
|
|
|
|
"request": request_info,
|
|
|
|
|
"detail": str(exc.description) if hasattr(exc, "description") else str(exc),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=429,
|
|
|
|
|
content={
|
|
|
|
|
"data": None,
|
|
|
|
|
"message": "Rate limit exceeded",
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
"error_id": error_id
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, RequestValidationError):
|
|
|
|
|
logging.error(
|
|
|
|
|
f"Validation error | Error: {str(exc.errors())} | Request: {request_info}",
|
|
|
|
|
extra={"error_category": "validation"},
|
|
|
|
|
logging.warning(
|
|
|
|
|
f"Validation error occurred | Error ID: {error_id}",
|
|
|
|
|
extra={
|
|
|
|
|
"error_id": error_id,
|
|
|
|
|
"error_category": "validation",
|
|
|
|
|
"errors": exc.errors(),
|
|
|
|
|
"request": request_info,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=422,
|
|
|
|
|
content={
|
|
|
|
|
"data": None,
|
|
|
|
|
"message": "Validation error",
|
|
|
|
|
"data": exc.errors(),
|
|
|
|
|
"message": "Validation Error",
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
"errors": [
|
|
|
|
|
ErrorDetail(
|
|
|
|
|
field=".".join(map(str, err["loc"])),
|
|
|
|
|
message=err["msg"],
|
|
|
|
|
code=err["type"],
|
|
|
|
|
).model_dump()
|
|
|
|
|
for err in exc.errors()
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"error_id": error_id
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, HTTPException):
|
|
|
|
|
logging.error(
|
|
|
|
|
f"HTTP exception | Code: {exc.status_code} | Error: {exc.detail} | Request: {request_info}",
|
|
|
|
|
extra={"error_category": "http"},
|
|
|
|
|
f"HTTP exception occurred | Error ID: {error_id}",
|
|
|
|
|
extra={
|
|
|
|
|
"error_id": error_id,
|
|
|
|
|
"error_category": "http",
|
|
|
|
|
"status_code": exc.status_code,
|
|
|
|
|
"detail": exc.detail if hasattr(exc, "detail") else str(exc),
|
|
|
|
|
"request": request_info,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=exc.status_code,
|
|
|
|
|
content={
|
|
|
|
|
"data": None,
|
|
|
|
|
"message": str(exc.detail),
|
|
|
|
|
"message": str(exc.detail) if hasattr(exc, "detail") else str(exc),
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
"errors": [
|
|
|
|
|
ErrorDetail(
|
|
|
|
|
message=str(exc.detail)
|
|
|
|
|
).model_dump()
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"error_id": error_id
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, SQLAlchemyError):
|
|
|
|
|
error_message, status_code = handle_sqlalchemy_error(exc)
|
|
|
|
|
logging.error(
|
|
|
|
|
f"Database Error | Error: {str(error_message)} | Request: {request_info}",
|
|
|
|
|
extra={"error_category": "database"},
|
|
|
|
|
f"Database error occurred | Error ID: {error_id}",
|
|
|
|
|
extra={
|
|
|
|
|
"error_id": error_id,
|
|
|
|
|
"error_category": "database",
|
|
|
|
|
"error_message": error_message,
|
|
|
|
|
"request": request_info,
|
|
|
|
|
"exception": str(exc),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
@ -156,42 +184,28 @@ def handle_exception(request: Request, exc: Exception):
|
|
|
|
|
"data": None,
|
|
|
|
|
"message": error_message,
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
"errors": [
|
|
|
|
|
ErrorDetail(
|
|
|
|
|
message=error_message
|
|
|
|
|
).model_dump()
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"error_id": error_id
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Log unexpected errors
|
|
|
|
|
error_message = f"{exc.__class__.__name__}: {str(exc)}"
|
|
|
|
|
error_traceback = exc.__traceback__
|
|
|
|
|
|
|
|
|
|
# Get file and line info if available
|
|
|
|
|
if error_traceback:
|
|
|
|
|
tb = error_traceback
|
|
|
|
|
while tb.tb_next:
|
|
|
|
|
tb = tb.tb_next
|
|
|
|
|
file_name = tb.tb_frame.f_code.co_filename
|
|
|
|
|
line_num = tb.tb_lineno
|
|
|
|
|
error_message = f"{error_message}\nFile {file_name}, line {line_num}"
|
|
|
|
|
|
|
|
|
|
logging.error(
|
|
|
|
|
f"Unexpected Error | Error: {error_message} | Request: {request_info}",
|
|
|
|
|
extra={"error_category": "unexpected"},
|
|
|
|
|
f"Unexpected error occurred | Error ID: {error_id}",
|
|
|
|
|
extra={
|
|
|
|
|
"error_id": error_id,
|
|
|
|
|
"error_category": "unexpected",
|
|
|
|
|
"error_message": str(exc),
|
|
|
|
|
"request": request_info,
|
|
|
|
|
},
|
|
|
|
|
exc_info=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=500,
|
|
|
|
|
content={
|
|
|
|
|
"data": None,
|
|
|
|
|
"message": error_message,
|
|
|
|
|
"message": "An unexpected error occurred",
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
"errors": [
|
|
|
|
|
ErrorDetail(
|
|
|
|
|
message=error_message
|
|
|
|
|
).model_dump()
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"error_id": error_id
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|