ALM-2 / backend /middleware /error_handler.py
ACA050's picture
Upload 520 files
2ed8996 verified
"""
Global Error Handler Middleware for AegisLM Backend.
Provides centralized error handling, logging, and consistent error responses
across all API endpoints.
"""
import logging
import traceback
from datetime import datetime
from typing import Union
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from schemas.response_schema import ErrorResponse
logger = logging.getLogger(__name__)
class GlobalErrorHandler(BaseHTTPMiddleware):
"""
Global error handler middleware.
Catches all exceptions and returns consistent error responses
with proper logging and error tracking.
"""
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
return response
except HTTPException as http_exc:
# Handle HTTP exceptions (validation, auth, etc.)
return self._handle_http_exception(http_exc, request)
except Exception as exc:
# Handle unexpected exceptions
return self._handle_unexpected_exception(exc, request)
def _handle_http_exception(self, exc: HTTPException, request: Request) -> JSONResponse:
"""Handle known HTTP exceptions."""
logger.warning(
f"HTTP Exception: {exc.status_code} - {exc.detail} - "
f"Path: {request.url.path} - Method: {request.method}"
)
error_response = ErrorResponse(
success=False,
error_code=f"HTTP_{exc.status_code}",
message=str(exc.detail),
error_details={
"status_code": exc.status_code,
"path": request.url.path,
"method": request.method
}
)
return JSONResponse(
status_code=exc.status_code,
content=error_response.dict()
)
def _handle_unexpected_exception(self, exc: Exception, request: Request) -> JSONResponse:
"""Handle unexpected exceptions."""
error_id = f"ERR_{id(exc)}"
# Log the full error for debugging
logger.error(
f"Unexpected Exception [{error_id}]: {str(exc)} - "
f"Path: {request.url.path} - Method: {request.method}\n"
f"Traceback: {traceback.format_exc()}"
)
# Return generic error to client
error_response = ErrorResponse(
success=False,
error_code="INTERNAL_SERVER_ERROR",
message="An internal server error occurred. Please try again later.",
error_details={
"error_id": error_id,
"path": request.url.path,
"method": request.method
},
timestamp=datetime.utcnow().isoformat()
)
return JSONResponse(
status_code=500,
content=error_response.dict()
)
def general_exception_handler(exc: Exception, request: Request) -> JSONResponse:
"""Handle all other exceptions."""
logger.error(f"General Exception: {str(exc)}")
error_response = ErrorResponse(
success=False,
error_code="INTERNAL_SERVER_ERROR",
message="An internal server error occurred. Please try again later.",
error_details={
"error_type": type(exc).__name__,
"path": request.url.path,
"method": request.method
},
timestamp=datetime.utcnow().isoformat()
)
return JSONResponse(
status_code=500,
content=error_response.dict()
)
def http_exception_handler(exc: HTTPException, request: Request) -> JSONResponse:
"""Handle HTTP exceptions."""
logger.warning(f"HTTP Exception: {exc.status_code} - {exc.detail}")
error_response = ErrorResponse(
success=False,
error_code="HTTP_ERROR",
message=str(exc.detail),
error_details={
"status_code": exc.status_code,
"path": request.url.path,
"method": request.method
},
timestamp=datetime.utcnow().isoformat()
)
return JSONResponse(
status_code=exc.status_code,
content=error_response.dict()
)
class ValidationErrorHandler:
"""
Specialized handler for validation errors.
"""
@staticmethod
def format_validation_error(validation_error) -> dict:
"""Format validation errors for consistent response."""
errors = []
if hasattr(validation_error, 'errors'):
for error in validation_error.errors():
field = '.'.join(str(x) for x in error['loc'])
errors.append({
'field': field,
'message': error['msg'],
'value': error.get('input')
})
return {
'success': False,
'error_code': 'VALIDATION_ERROR',
'message': 'Invalid input data',
'error_details': {
'validation_errors': errors
}
}
class DatabaseErrorHandler:
"""
Specialized handler for database errors.
"""
@staticmethod
def handle_database_error(exc: Exception, operation: str) -> dict:
"""Handle database-related errors."""
logger.error(f"Database Error during {operation}: {str(exc)}")
error_type = "DATABASE_CONNECTION_ERROR" if "connection" in str(exc).lower() else "DATABASE_OPERATION_ERROR"
return {
'success': False,
'error_code': error_type,
'message': 'Database operation failed. Please try again later.',
'error_details': {
'operation': operation,
'error_type': type(exc).__name__
}
}
class AuthenticationErrorHandler:
"""
Specialized handler for authentication errors.
"""
@staticmethod
def handle_auth_error(exc: Exception) -> dict:
"""Handle authentication-related errors."""
logger.warning(f"Authentication Error: {str(exc)}")
return {
'success': False,
'error_code': 'AUTHENTICATION_ERROR',
'message': 'Authentication failed. Please check your credentials.',
'error_details': {
'error_type': type(exc).__name__
}
}
class RateLimitErrorHandler:
"""
Specialized handler for rate limiting errors.
"""
@staticmethod
def handle_rate_limit_error(exc: Exception, retry_after: int = None) -> dict:
"""Handle rate limiting errors."""
logger.info(f"Rate Limit Error: {str(exc)}")
response = {
'success': False,
'error_code': 'RATE_LIMIT_EXCEEDED',
'message': 'Rate limit exceeded. Please try again later.',
'error_details': {
'error_type': type(exc).__name__
}
}
if retry_after:
response['error_details']['retry_after'] = retry_after
return response
# Utility functions for common error scenarios
def create_not_found_error(resource: str, identifier: Union[str, int]) -> dict:
"""Create a standardized not found error."""
return {
'success': False,
'error_code': 'RESOURCE_NOT_FOUND',
'message': f'{resource} not found: {identifier}',
'error_details': {
'resource': resource,
'identifier': str(identifier)
}
}
def create_permission_error(action: str, resource: str) -> dict:
"""Create a standardized permission error."""
return {
'success': False,
'error_code': 'PERMISSION_DENIED',
'message': f'Permission denied for {action} on {resource}',
'error_details': {
'action': action,
'resource': resource
}
}
def create_business_rule_error(rule: str, message: str) -> dict:
"""Create a standardized business rule violation error."""
return {
'success': False,
'error_code': 'BUSINESS_RULE_VIOLATION',
'message': message,
'error_details': {
'rule': rule
}
}