Spaces:
Running
Running
File size: 3,279 Bytes
69fb140 6c78660 69fb140 6c78660 69fb140 |
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 |
"""
全域異常處理中間件
統一處理所有未捕獲的異常,返回標準化錯誤回應
"""
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
|