|
|
|
@ -15,6 +15,9 @@ from sqlalchemy.exc import (DataError, DBAPIError, IntegrityError,
|
|
|
|
|
|
|
|
|
|
|
|
from src.enums import ResponseStatus
|
|
|
|
from src.enums import ResponseStatus
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class ErrorDetail(BaseModel):
|
|
|
|
class ErrorDetail(BaseModel):
|
|
|
|
field: Optional[str] = None
|
|
|
|
field: Optional[str] = None
|
|
|
|
@ -57,7 +60,7 @@ def get_request_context(request: Request):
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
"endpoint": request.url.path,
|
|
|
|
"endpoint": request.url.path,
|
|
|
|
"url": request.url,
|
|
|
|
"url": str(request.url),
|
|
|
|
"method": request.method,
|
|
|
|
"method": request.method,
|
|
|
|
"remote_addr": get_client_ip(),
|
|
|
|
"remote_addr": get_client_ip(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -71,7 +74,6 @@ def handle_sqlalchemy_error(error: SQLAlchemyError):
|
|
|
|
original_error = error.orig
|
|
|
|
original_error = error.orig
|
|
|
|
except AttributeError:
|
|
|
|
except AttributeError:
|
|
|
|
original_error = None
|
|
|
|
original_error = None
|
|
|
|
print(original_error)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(error, IntegrityError):
|
|
|
|
if isinstance(error, IntegrityError):
|
|
|
|
if "unique constraint" in str(error).lower():
|
|
|
|
if "unique constraint" in str(error).lower():
|
|
|
|
@ -95,7 +97,7 @@ def handle_sqlalchemy_error(error: SQLAlchemyError):
|
|
|
|
return "Database error.", 500
|
|
|
|
return "Database error.", 500
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
# Log the full error for debugging purposes
|
|
|
|
# Log the full error for debugging purposes
|
|
|
|
logging.error(f"Unexpected database error: {str(error)}")
|
|
|
|
log.error(f"Unexpected database error: {str(error)}")
|
|
|
|
return "An unexpected database error occurred.", 500
|
|
|
|
return "An unexpected database error occurred.", 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -106,28 +108,62 @@ def handle_exception(request: Request, exc: Exception):
|
|
|
|
request_info = get_request_context(request)
|
|
|
|
request_info = get_request_context(request)
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, RateLimitExceeded):
|
|
|
|
if isinstance(exc, RateLimitExceeded):
|
|
|
|
_rate_limit_exceeded_handler(request, exc)
|
|
|
|
return _rate_limit_exceeded_handler(request, exc)
|
|
|
|
if isinstance(exc, HTTPException):
|
|
|
|
|
|
|
|
logging.error(
|
|
|
|
if isinstance(exc, RequestValidationError):
|
|
|
|
f"HTTP exception | Code: {exc.status_code} | Error: {exc.detail} | Request: {request_info}",
|
|
|
|
log.error(
|
|
|
|
extra={"error_category": "http"},
|
|
|
|
"Validation error occurred",
|
|
|
|
|
|
|
|
extra={
|
|
|
|
|
|
|
|
"error_category": "validation",
|
|
|
|
|
|
|
|
"errors": exc.errors(),
|
|
|
|
|
|
|
|
"request": request_info,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
|
|
status_code=422,
|
|
|
|
|
|
|
|
content={
|
|
|
|
|
|
|
|
"data": None,
|
|
|
|
|
|
|
|
"message": "Validation Error",
|
|
|
|
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
|
|
|
|
"errors": exc.errors(),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, (HTTPException, StarletteHTTPException)):
|
|
|
|
|
|
|
|
log.error(
|
|
|
|
|
|
|
|
"HTTP exception occurred",
|
|
|
|
|
|
|
|
extra={
|
|
|
|
|
|
|
|
"error_category": "http",
|
|
|
|
|
|
|
|
"status_code": exc.status_code,
|
|
|
|
|
|
|
|
"detail": exc.detail if hasattr(exc, "detail") else str(exc),
|
|
|
|
|
|
|
|
"request": request_info,
|
|
|
|
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
return JSONResponse(
|
|
|
|
status_code=exc.status_code,
|
|
|
|
status_code=exc.status_code,
|
|
|
|
content={
|
|
|
|
content={
|
|
|
|
"data": None,
|
|
|
|
"data": None,
|
|
|
|
"message": str(exc.detail),
|
|
|
|
"message": str(exc.detail) if hasattr(exc, "detail") else str(exc),
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
"status": ResponseStatus.ERROR,
|
|
|
|
"errors": [ErrorDetail(message=str(exc.detail)).model_dump()],
|
|
|
|
"errors": [
|
|
|
|
|
|
|
|
ErrorDetail(
|
|
|
|
|
|
|
|
message=str(exc.detail) if hasattr(exc, "detail") else str(exc)
|
|
|
|
|
|
|
|
).model_dump()
|
|
|
|
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if isinstance(exc, SQLAlchemyError):
|
|
|
|
if isinstance(exc, SQLAlchemyError):
|
|
|
|
error_message, status_code = handle_sqlalchemy_error(exc)
|
|
|
|
error_message, status_code = handle_sqlalchemy_error(exc)
|
|
|
|
logging.error(
|
|
|
|
log.error(
|
|
|
|
f"Database Error | Error: {str(error_message)} | Request: {request_info}",
|
|
|
|
"Database error occurred",
|
|
|
|
extra={"error_category": "database"},
|
|
|
|
extra={
|
|
|
|
|
|
|
|
"error_category": "database",
|
|
|
|
|
|
|
|
"error_message": error_message,
|
|
|
|
|
|
|
|
"request": request_info,
|
|
|
|
|
|
|
|
"exception": str(exc),
|
|
|
|
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
return JSONResponse(
|
|
|
|
@ -141,9 +177,14 @@ def handle_exception(request: Request, exc: Exception):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Log unexpected errors
|
|
|
|
# Log unexpected errors
|
|
|
|
logging.error(
|
|
|
|
log.error(
|
|
|
|
f"Unexpected Error | Error: {str(exc)} | Request: {request_info}",
|
|
|
|
"Unexpected error occurred",
|
|
|
|
extra={"error_category": "unexpected"},
|
|
|
|
extra={
|
|
|
|
|
|
|
|
"error_category": "unexpected",
|
|
|
|
|
|
|
|
"error_message": str(exc),
|
|
|
|
|
|
|
|
"request": request_info,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
exc_info=True,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
return JSONResponse(
|
|
|
|
|