Download app/core/error_handler.py from bg5/zai: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/spaces/bg5/zai/resolve/main/app/core/error_handler.py
- Command line
-
hf download hf://spaces/bg5/zai/app/core/error_handler.py
-
curl -L -o error_handler.py https://huggingface.co/spaces/bg5/zai/resolve/main/app/core/error_handler.py
6.79 kB
| #!/usr/bin/env python | |
| # -*- coding: utf-8 -*- | |
| """全局错误处理器。 | |
| 提供统一的异常处理机制: | |
| - AppError 及其子类 → 结构化错误响应 | |
| - RequestValidationError → 验证错误响应 | |
| - HTTPException → 保持原有行为 | |
| - Exception → 兜底处理 | |
| 所有错误响应遵循 OpenAI API 格式。 | |
| """ | |
| import time | |
| from typing import Any, Callable, Dict | |
| from fastapi import Request, status | |
| from fastapi.encoders import jsonable_encoder | |
| from fastapi.exceptions import HTTPException, RequestValidationError | |
| from fastapi.responses import JSONResponse | |
| from app.exceptions import AppError | |
| from app.utils.logger import logger, log_exception | |
| # ----------------------------------------------------------------------- | |
| # OpenAI 兼容错误响应格式 | |
| # ----------------------------------------------------------------------- | |
| def create_error_response( | |
| message: str, | |
| error_type: str, | |
| code: str, | |
| status_code: int = 500, | |
| details: Dict[str, Any] | None = None, | |
| ) -> Dict[str, Any]: | |
| """创建 OpenAI 兼容的错误响应体。""" | |
| error: Dict[str, Any] = { | |
| "message": message, | |
| "type": error_type, | |
| "code": code, | |
| } | |
| if details: | |
| error["details"] = details | |
| return {"error": error} | |
| # ----------------------------------------------------------------------- | |
| # 异常处理器 | |
| # ----------------------------------------------------------------------- | |
| async def app_error_handler(request: Request, exc: AppError) -> JSONResponse: | |
| """处理 AppError 及其子类。 | |
| 记录错误日志并返回结构化的错误响应。 | |
| """ | |
| # 根据状态码选择日志级别 | |
| if exc.status_code >= 500: | |
| log_func = logger.error | |
| elif exc.status_code >= 400: | |
| log_func = logger.warning | |
| else: | |
| log_func = logger.info | |
| log_func( | |
| "API error: %s (code=%s, status=%d, path=%s)", | |
| exc.message, | |
| exc.code, | |
| exc.status_code, | |
| request.url.path, | |
| extra={ | |
| "error_code": exc.code, | |
| "status_code": exc.status_code, | |
| "path": request.url.path, | |
| "method": request.method, | |
| "details": exc.details, | |
| }, | |
| ) | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content=exc.to_dict(), | |
| ) | |
| async def validation_error_handler( | |
| request: Request, | |
| exc: RequestValidationError, | |
| ) -> JSONResponse: | |
| """处理 FastAPI 请求验证错误。""" | |
| errors = exc.errors() | |
| # 提取第一个错误作为主要消息 | |
| if errors: | |
| first_error = errors[0] | |
| field = ".".join(str(loc) for loc in first_error.get("loc", [])) | |
| message = first_error.get("msg", "Validation failed") | |
| if field: | |
| message = f"{field}: {message}" | |
| else: | |
| message = "Request validation failed" | |
| logger.warning( | |
| "Validation error: %s (path=%s)", | |
| message, | |
| request.url.path, | |
| extra={ | |
| "path": request.url.path, | |
| "method": request.method, | |
| "validation_errors": errors, | |
| }, | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, | |
| content=create_error_response( | |
| message=message, | |
| error_type="ValidationError", | |
| code="validation_error", | |
| status_code=422, | |
| details={"errors": errors}, | |
| ), | |
| ) | |
| async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse: | |
| """处理 FastAPI HTTPException。 | |
| 将 HTTPException 转换为 OpenAI 兼容格式。 | |
| """ | |
| # 特殊处理重定向 | |
| if exc.status_code in (301, 302, 303, 307, 308): | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={"detail": exc.detail}, | |
| headers=exc.headers, | |
| ) | |
| logger.warning( | |
| "HTTP exception: %s (status=%d, path=%s)", | |
| exc.detail, | |
| exc.status_code, | |
| request.url.path, | |
| ) | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content=create_error_response( | |
| message=str(exc.detail), | |
| error_type="HTTPException", | |
| code=f"http_{exc.status_code}", | |
| status_code=exc.status_code, | |
| ), | |
| headers=exc.headers, | |
| ) | |
| async def generic_error_handler(request: Request, exc: Exception) -> JSONResponse: | |
| """兜底处理未知异常。 | |
| 记录完整堆栈,返回通用错误响应。 | |
| """ | |
| log_exception( | |
| logger, | |
| "Unhandled exception: %s (path=%s, method=%s)", | |
| type(exc).__name__, | |
| request.url.path, | |
| request.method, | |
| error=exc, | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| content=create_error_response( | |
| message="Internal server error", | |
| error_type="InternalServerError", | |
| code="internal_error", | |
| ), | |
| ) | |
| # ----------------------------------------------------------------------- | |
| # 流式响应错误处理 | |
| # ----------------------------------------------------------------------- | |
| def format_sse_error( | |
| message: str, | |
| code: str = "internal_error", | |
| error_type: str = "InternalError", | |
| ) -> str: | |
| """格式化 SSE 错误响应。 | |
| 用于流式响应中的错误传递。 | |
| Args: | |
| message: 错误消息 | |
| code: 错误码 | |
| error_type: 错误类型 | |
| Returns: | |
| SSE 格式的错误字符串 | |
| """ | |
| import json | |
| error_data = { | |
| "error": { | |
| "message": message, | |
| "type": error_type, | |
| "code": code, | |
| } | |
| } | |
| return f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n" | |
| def format_sse_done() -> str: | |
| """返回 SSE 结束标记。""" | |
| return "data: [DONE]\n\n" | |
| # ----------------------------------------------------------------------- | |
| # 注册函数 | |
| # ----------------------------------------------------------------------- | |
| def register_exception_handlers(app) -> None: | |
| """注册所有异常处理器到 FastAPI 应用。 | |
| Args: | |
| app: FastAPI 应用实例 | |
| """ | |
| from fastapi.exceptions import HTTPException, RequestValidationError | |
| app.add_exception_handler(AppError, app_error_handler) | |
| app.add_exception_handler(RequestValidationError, validation_error_handler) | |
| app.add_exception_handler(HTTPException, http_exception_handler) | |
| app.add_exception_handler(Exception, generic_error_handler) | |
| logger.info("✅ Global exception handlers registered") | |