| """ |
| 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: |
| |
| return self._handle_http_exception(http_exc, request) |
| except Exception as exc: |
| |
| 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)}" |
| |
| |
| logger.error( |
| f"Unexpected Exception [{error_id}]: {str(exc)} - " |
| f"Path: {request.url.path} - Method: {request.method}\n" |
| f"Traceback: {traceback.format_exc()}" |
| ) |
| |
| |
| 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 |
|
|
|
|
| |
| 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 |
| } |
| } |
|
|