Spaces:
Running
Running
| """ | |
| 全域異常處理中間件 | |
| 統一處理所有未捕獲的異常,返回標準化錯誤回應 | |
| """ | |
| import logging | |
| import traceback | |
| from typing import Callable | |
| from fastapi import Request, Response | |
| from fastapi.responses import JSONResponse | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| from core.exceptions import BloomWareException, handle_exception | |
| from core.logging import get_logger | |
| logger = get_logger("middleware.exception_handler") | |
| class ExceptionHandlerMiddleware(BaseHTTPMiddleware): | |
| """ | |
| 全域異常處理中間件 | |
| 功能: | |
| 1. 捕獲所有未處理的異常 | |
| 2. 轉換為標準化 JSON 錯誤回應 | |
| 3. 記錄錯誤日誌(生產環境隱藏堆疊) | |
| """ | |
| async def dispatch(self, request: Request, call_next: Callable) -> Response: | |
| try: | |
| response = await call_next(request) | |
| return response | |
| except BloomWareException as e: | |
| # 已知的業務異常 | |
| logger.warning(f"業務異常: {e.code} - {e.message}") | |
| return e.to_response() | |
| except Exception as e: | |
| # 未知異常 | |
| logger.error(f"未處理的異常: {type(e).__name__}: {e}") | |
| logger.debug(f"堆疊追蹤:\n{traceback.format_exc()}") | |
| # 返回標準化錯誤回應 | |
| return JSONResponse( | |
| status_code=500, | |
| content={ | |
| "success": False, | |
| "error": { | |
| "code": "INTERNAL_ERROR", | |
| "message": "內部伺服器錯誤", | |
| "details": {} | |
| } | |
| } | |
| ) | |
| class RequestLoggingMiddleware(BaseHTTPMiddleware): | |
| """ | |
| 請求日誌中間件 | |
| 功能: | |
| 1. 記錄所有 API 請求 | |
| 2. 計算請求處理時間 | |
| 3. 記錄回應狀態碼 | |
| """ | |
| async def dispatch(self, request: Request, call_next: Callable) -> Response: | |
| import time | |
| start_time = time.time() | |
| method = request.method | |
| path = request.url.path | |
| # 跳過健康檢查和靜態資源的日誌 | |
| skip_paths = ["/health", "/static/", "/favicon.ico"] | |
| should_log = not any(path.startswith(p) for p in skip_paths) | |
| try: | |
| response = await call_next(request) | |
| process_time = (time.time() - start_time) * 1000 | |
| # 只記錄錯誤或慢請求(超過 1000ms) | |
| if should_log: | |
| if response.status_code >= 400: | |
| logger.warning( | |
| f"{method} {path} - {response.status_code} - {process_time:.2f}ms" | |
| ) | |
| elif process_time > 1000: | |
| logger.warning( | |
| f"{method} {path} - SLOW - {response.status_code} - {process_time:.2f}ms" | |
| ) | |
| # 添加處理時間到回應標頭 | |
| response.headers["X-Process-Time"] = f"{process_time:.2f}ms" | |
| return response | |
| except Exception as e: | |
| process_time = (time.time() - start_time) * 1000 | |
| if should_log: | |
| logger.error( | |
| f"{method} {path} - ERROR - {process_time:.2f}ms - {e}" | |
| ) | |
| raise | |