From af476ff21ed7a9334101e40674dacdbad6078228 Mon Sep 17 00:00:00 2001 From: fawney19 Date: Fri, 19 Dec 2025 15:29:48 +0800 Subject: [PATCH] feat: enhance error logging and upstream response tracking for provider failures --- src/api/handlers/base/chat_handler_base.py | 30 +++++++++++++++-- src/core/exceptions.py | 4 +++ src/main.py | 32 +------------------ .../orchestration/fallback_orchestrator.py | 6 ++++ src/services/request/result.py | 8 ++++- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/api/handlers/base/chat_handler_base.py b/src/api/handlers/base/chat_handler_base.py index ceb10d3..a6e1f6d 100644 --- a/src/api/handlers/base/chat_handler_base.py +++ b/src/api/handlers/base/chat_handler_base.py @@ -639,6 +639,8 @@ class ChatHandlerBase(BaseMessageHandler, ABC): logger.info(f" [{self.request_id}] 发送非流式请求: Provider={provider.name}, " f"模型={model} -> {mapped_model or '无映射'}") + logger.debug(f" [{self.request_id}] 请求URL: {url}") + logger.debug(f" [{self.request_id}] 请求体stream字段: {provider_payload.get('stream', 'N/A')}") # 创建 HTTP 客户端(支持代理配置) from src.clients.http_client import HTTPClientPool @@ -662,10 +664,32 @@ class ChatHandlerBase(BaseMessageHandler, ABC): response_headers=response_headers, ) elif resp.status_code >= 500: - raise ProviderNotAvailableException(f"提供商服务不可用: {provider.name}") - elif resp.status_code != 200: + # 记录响应体以便调试 + error_body = "" + try: + error_body = resp.text[:1000] + logger.error(f" [{self.request_id}] 上游返回5xx错误: status={resp.status_code}, body={error_body[:500]}") + except Exception: + pass raise ProviderNotAvailableException( - f"提供商返回错误: {provider.name}, 状态: {resp.status_code}" + f"提供商服务不可用: {provider.name}", + provider_name=str(provider.name), + upstream_status=resp.status_code, + upstream_response=error_body, + ) + elif resp.status_code != 200: + # 记录非200响应以便调试 + error_body = "" + try: + error_body = resp.text[:1000] + logger.warning(f" [{self.request_id}] 上游返回非200: status={resp.status_code}, body={error_body[:500]}") + except Exception: + pass + raise ProviderNotAvailableException( + f"提供商返回错误: {provider.name}, 状态: {resp.status_code}", + provider_name=str(provider.name), + upstream_status=resp.status_code, + upstream_response=error_body, ) response_json = resp.json() diff --git a/src/core/exceptions.py b/src/core/exceptions.py index 6c0000b..6f6eeb5 100644 --- a/src/core/exceptions.py +++ b/src/core/exceptions.py @@ -188,12 +188,16 @@ class ProviderNotAvailableException(ProviderException): message: str, provider_name: Optional[str] = None, request_metadata: Optional[Any] = None, + upstream_status: Optional[int] = None, + upstream_response: Optional[str] = None, ): super().__init__( message=message, provider_name=provider_name, request_metadata=request_metadata, ) + self.upstream_status = upstream_status + self.upstream_response = upstream_response class ProviderTimeoutException(ProviderException): diff --git a/src/main.py b/src/main.py index dd32e77..1f76ff0 100644 --- a/src/main.py +++ b/src/main.py @@ -4,13 +4,10 @@ """ from contextlib import asynccontextmanager -from pathlib import Path import uvicorn -from fastapi import FastAPI, HTTPException, Request +from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import FileResponse -from fastapi.staticfiles import StaticFiles from src.api.admin import router as admin_router from src.api.announcements import router as announcement_router @@ -299,33 +296,6 @@ app.include_router(dashboard_router) # 仪表盘端点 app.include_router(public_router) # 公开API端点(用户可查看提供商和模型) app.include_router(monitoring_router) # 监控端点 -# 静态文件服务(前端构建产物) -# 检查前端构建目录是否存在 -frontend_dist = Path(__file__).parent.parent / "frontend" / "dist" -if frontend_dist.exists(): - # 挂载静态资源目录 - app.mount("/assets", StaticFiles(directory=str(frontend_dist / "assets")), name="assets") - - # SPA catch-all路由 - 必须放在最后 - @app.get("/{full_path:path}") - async def serve_spa(request: Request, full_path: str): - """ - 处理所有未匹配的GET请求,返回index.html供前端路由处理 - 仅对非API路径生效 - """ - # 如果是API路径,不处理 - if full_path in {"api", "v1"} or full_path.startswith(("api/", "v1/")): - raise HTTPException(status_code=404, detail="Not Found") - - # 返回index.html,让前端路由处理 - index_file = frontend_dist / "index.html" - if index_file.exists(): - return FileResponse(str(index_file)) - else: - raise HTTPException(status_code=404, detail="Frontend not built") - -else: - logger.warning("前端构建目录不存在,前端路由将无法使用") def main(): diff --git a/src/services/orchestration/fallback_orchestrator.py b/src/services/orchestration/fallback_orchestrator.py index 1574a2c..00ce509 100644 --- a/src/services/orchestration/fallback_orchestrator.py +++ b/src/services/orchestration/fallback_orchestrator.py @@ -427,6 +427,9 @@ class FallbackOrchestrator: ) # str(cause) 可能为空(如 httpx 超时异常),使用 repr() 作为备用 error_msg = str(cause) or repr(cause) + # 如果是 ProviderNotAvailableException,附加上游响应 + if hasattr(cause, "upstream_response") and cause.upstream_response: + error_msg = f"{error_msg} | 上游响应: {cause.upstream_response[:500]}" RequestCandidateService.mark_candidate_failed( db=self.db, candidate_id=candidate_record_id, @@ -439,6 +442,9 @@ class FallbackOrchestrator: # 未知错误:记录失败并抛出 error_msg = str(cause) or repr(cause) + # 如果是 ProviderNotAvailableException,附加上游响应 + if hasattr(cause, "upstream_response") and cause.upstream_response: + error_msg = f"{error_msg} | 上游响应: {cause.upstream_response[:500]}" RequestCandidateService.mark_candidate_failed( db=self.db, candidate_id=candidate_record_id, diff --git a/src/services/request/result.py b/src/services/request/result.py index 8f3c729..1f91994 100644 --- a/src/services/request/result.py +++ b/src/services/request/result.py @@ -289,11 +289,17 @@ class RequestResult: status_code = 500 error_type = "internal_error" + # 构建错误消息,包含上游响应信息 + error_message = str(exception) + if isinstance(exception, ProviderNotAvailableException): + if exception.upstream_response: + error_message = f"{error_message} | 上游响应: {exception.upstream_response[:500]}" + return cls( status=RequestStatus.FAILED, metadata=metadata, status_code=status_code, - error_message=str(exception), + error_message=error_message, error_type=error_type, response_time_ms=response_time_ms, is_stream=is_stream,