Files
000-platform/backend/app/middleware/exception_handler.py
111 6c6c48cf71
All checks were successful
continuous-integration/drone/push Build is passing
feat: 新增告警、成本、配额、微信模块及缓存服务
- 新增告警模块 (alerts): 告警规则配置与触发
- 新增成本管理模块 (cost): 成本统计与分析
- 新增配额模块 (quota): 配额管理与限制
- 新增微信模块 (wechat): 微信相关功能接口
- 新增缓存服务 (cache): Redis 缓存封装
- 新增请求日志中间件 (request_logger)
- 新增异常处理和链路追踪中间件
- 更新 dashboard 前端展示
- 更新 SDK stats_client 功能
2026-01-24 16:53:47 +08:00

129 lines
3.9 KiB
Python

"""
统一异常处理
捕获所有异常,返回统一格式的错误响应,包含 TraceID。
"""
import logging
import traceback
from typing import Union
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from .trace import get_trace_id
logger = logging.getLogger(__name__)
class ErrorCode:
"""错误码常量"""
BAD_REQUEST = "BAD_REQUEST"
UNAUTHORIZED = "UNAUTHORIZED"
FORBIDDEN = "FORBIDDEN"
NOT_FOUND = "NOT_FOUND"
VALIDATION_ERROR = "VALIDATION_ERROR"
RATE_LIMITED = "RATE_LIMITED"
INTERNAL_ERROR = "INTERNAL_ERROR"
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"
GATEWAY_ERROR = "GATEWAY_ERROR"
STATUS_TO_ERROR_CODE = {
400: ErrorCode.BAD_REQUEST,
401: ErrorCode.UNAUTHORIZED,
403: ErrorCode.FORBIDDEN,
404: ErrorCode.NOT_FOUND,
422: ErrorCode.VALIDATION_ERROR,
429: ErrorCode.RATE_LIMITED,
500: ErrorCode.INTERNAL_ERROR,
502: ErrorCode.GATEWAY_ERROR,
503: ErrorCode.SERVICE_UNAVAILABLE,
}
def create_error_response(
status_code: int,
code: str,
message: str,
trace_id: str = None,
details: dict = None
) -> JSONResponse:
"""创建统一格式的错误响应"""
if trace_id is None:
trace_id = get_trace_id()
error_body = {
"code": code,
"message": message,
"trace_id": trace_id
}
if details:
error_body["details"] = details
return JSONResponse(
status_code=status_code,
content={"success": False, "error": error_body},
headers={"X-Trace-ID": trace_id}
)
async def http_exception_handler(request: Request, exc: Union[HTTPException, StarletteHTTPException]):
"""处理 HTTP 异常"""
trace_id = get_trace_id()
status_code = exc.status_code
error_code = STATUS_TO_ERROR_CODE.get(status_code, ErrorCode.INTERNAL_ERROR)
message = exc.detail if isinstance(exc.detail, str) else str(exc.detail)
logger.warning(f"[{trace_id}] HTTP {status_code}: {message}")
return create_error_response(
status_code=status_code,
code=error_code,
message=message,
trace_id=trace_id
)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""处理请求验证错误"""
trace_id = get_trace_id()
errors = exc.errors()
error_messages = [f"{'.'.join(str(l) for l in e['loc'])}: {e['msg']}" for e in errors]
logger.warning(f"[{trace_id}] 验证错误: {error_messages}")
return create_error_response(
status_code=422,
code=ErrorCode.VALIDATION_ERROR,
message="请求参数验证失败",
trace_id=trace_id,
details={"validation_errors": error_messages}
)
async def generic_exception_handler(request: Request, exc: Exception):
"""处理所有未捕获的异常"""
trace_id = get_trace_id()
logger.error(f"[{trace_id}] 未捕获异常: {type(exc).__name__}: {exc}")
logger.error(f"[{trace_id}] 堆栈:\n{traceback.format_exc()}")
return create_error_response(
status_code=500,
code=ErrorCode.INTERNAL_ERROR,
message="服务器内部错误,请稍后重试",
trace_id=trace_id
)
def setup_exception_handlers(app: FastAPI):
"""配置 FastAPI 应用的异常处理器"""
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(Exception, generic_exception_handler)
logger.info("异常处理器已配置")