File size: 4,834 Bytes
4a2ab42
11df5d5
4a2ab42
 
 
 
 
 
 
 
11df5d5
 
 
 
 
 
95c4c3b
 
4a2ab42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95c4c3b
 
 
 
4a2ab42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95c4c3b
4a2ab42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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)