import os import sys import traceback from fastapi import FastAPI, HTTPException, Request from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException from core.api_models import create_error_response from core.logging import log_error # Add backend directory to path for i18n module _backend_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "backend") if _backend_path not in sys.path: sys.path.insert(0, _backend_path) from i18n import ErrorMessages # noqa: E402 from locale_utils import get_locale_from_request # noqa: E402 def setup_exception_handlers(app: FastAPI): environment = os.getenv("ENVIRONMENT", "development").lower() @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """Handle HTTP exceptions with structured logging and localized response""" locale = get_locale_from_request(request) # Get localized error message based on status code localized_detail = exc.detail if exc.status_code == 401: localized_detail = ErrorMessages.unauthorized(locale) elif exc.status_code == 403: localized_detail = ErrorMessages.forbidden(locale) elif exc.status_code == 404: localized_detail = ErrorMessages.not_found(locale) elif exc.status_code >= 500: if environment == "production": localized_detail = ErrorMessages.server_error(locale) else: localized_detail = exc.detail log_error( "http_exception", f"HTTP {exc.status_code}: {localized_detail}", { "status_code": exc.status_code, "path": str(request.url), "method": request.method, "client_ip": request.client.host if request.client else None, "locale": locale, }, ) # Return standardized error response with localized message error_response = create_error_response( status_code=exc.status_code, detail=localized_detail, error_type="http_exception", request=request, ) return JSONResponse(status_code=exc.status_code, content=error_response) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """Handle unexpected exceptions with structured logging and localized response""" locale = get_locale_from_request(request) localized_error_message = ErrorMessages.unexpected_error(locale) error_details = { "type": type(exc).__name__, "message": str(exc), "traceback": traceback.format_exc(), "path": str(request.url), "method": request.method, "client_ip": request.client.host if request.client else None, "locale": locale, } log_error("unexpected_error", f"Unexpected error: {exc!s}", error_details) # Don't expose internal error details in production if environment == "production": error_response = create_error_response( status_code=500, detail=localized_error_message, error_type="internal_server_error", request=request, ) return JSONResponse(status_code=500, content=error_response) else: # Show full details in development error_response = create_error_response( status_code=500, detail=f"{localized_error_message} (Development: {exc!s})", error_type="unexpected_error", request=request, ) # Add traceback for development error_response["error"]["traceback"] = traceback.format_exc() return JSONResponse(status_code=500, content=error_response) @app.exception_handler(StarletteHTTPException) async def starlette_exception_handler(request: Request, exc: StarletteHTTPException): """Handle Starlette HTTP exceptions with standardized response""" log_error( "starlette_exception", f"Starlette HTTP {exc.status_code}: {exc.detail}", { "status_code": exc.status_code, "path": str(request.url), "method": request.method, }, ) # Return standardized error response error_response = create_error_response( status_code=exc.status_code, detail=exc.detail, error_type="starlette_exception", request=request, ) return JSONResponse(status_code=exc.status_code, content=error_response)