feat(error_handling): 增强 API 错误处理和日志记录

- 扩展 ErrorLog 数据模型,增加 model_name, error_type, error_code 字段,以记录更详细的错误信息。
- 在 GeminiChatService 和 OpenAIChatService 中添加了 try-except 块,用于捕获 API 调用(包括普通和流式调用)时发生的异常。
- 实现从异常消息中通过正则表达式提取 HTTP 状态码的功能。
- 调用 add_error_log 服务将详细的错误信息(包括模型、错误类型、代码、请求体)持久化到数据库。
- 更新了 error_logs 前端页面,增加显示模型名称列及详情。
- 优化数据库连接池配置 (pool_recycle=3600),提高连接稳定性。
This commit is contained in:
snaily
2025-04-10 15:40:02 +08:00
parent 0f28173b0e
commit d94d24f96c
7 changed files with 103 additions and 10 deletions

View File

@@ -22,8 +22,9 @@ metadata = MetaData()
# 创建基类
Base = declarative_base(metadata=metadata)
# 创建数据库连接池
database = Database(DATABASE_URL)
# 创建数据库连接池,并配置连接池参数
# pool_recycle=3600: 回收空闲超过1小时的连接防止MySQL服务器超时断开
database = Database(DATABASE_URL, min_size=5, max_size=20, pool_recycle=3600)
async def connect_to_db():

View File

@@ -32,7 +32,10 @@ class ErrorLog(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
gemini_key = Column(String(100), nullable=True, comment="Gemini API密钥")
model_name = Column(String(100), nullable=True, comment="模型名称")
error_type = Column(String(50), nullable=True, comment="错误类型")
error_log = Column(Text, nullable=True, comment="错误日志")
error_code = Column(Integer, nullable=True, comment="错误代码")
request_msg = Column(JSON, nullable=True, comment="请求消息")
request_time = Column(DateTime, default=datetime.datetime.now, comment="请求时间")

View File

@@ -101,7 +101,10 @@ async def update_setting(key: str, value: str, description: Optional[str] = None
async def add_error_log(
gemini_key: Optional[str] = None,
model_name: Optional[str] = None,
error_type: Optional[str] = None,
error_log: Optional[str] = None,
error_code: Optional[int] = None,
request_msg: Optional[Union[Dict[str, Any], str]] = None
) -> bool:
"""
@@ -110,6 +113,7 @@ async def add_error_log(
Args:
gemini_key: Gemini API密钥
error_log: 错误日志
error_code: 错误代码 (例如 HTTP 状态码)
request_msg: 请求消息
Returns:
@@ -132,7 +136,10 @@ async def add_error_log(
insert(ErrorLog)
.values(
gemini_key=gemini_key,
error_type=error_type,
error_log=error_log,
model_name=model_name,
error_code=error_code,
request_msg=request_msg_json,
request_time=datetime.datetime.now()
)

View File

@@ -1,6 +1,7 @@
# app/services/chat_service.py
import json
import re
from typing import Any, AsyncGenerator, Dict, List
from app.config.config import settings
@@ -10,6 +11,7 @@ from app.handler.stream_optimizer import gemini_optimizer
from app.log.logger import get_gemini_logger
from app.service.client.api_client import GeminiApiClient
from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log
logger = get_gemini_logger()
@@ -145,8 +147,27 @@ class GeminiChatService:
) -> Dict[str, Any]:
"""生成内容"""
payload = _build_payload(model, request)
response = await self.api_client.generate_content(payload, model, api_key)
return self.response_handler.handle_response(response, model, stream=False)
try:
response = await self.api_client.generate_content(payload, model, api_key)
return self.response_handler.handle_response(response, model, stream=False)
except Exception as e:
logger.error(f"Normal API call failed with error: {str(e)}")
error_code = None
error_log_msg = str(e)
# 尝试从异常消息中解析状态码
match = re.search(r"status code (\d+)", error_log_msg)
if match:
error_code = int(match.group(1))
await add_error_log(
gemini_key=api_key,
model_name=model,
error_type="gemini_chat_service",
error_log=error_log_msg,
error_code=error_code,
request_msg=payload
)
raise e # 重新抛出异常,以便上层处理
async def stream_generate_content(
self, model: str, request: GeminiRequest, api_key: str
@@ -185,9 +206,25 @@ class GeminiChatService:
break
except Exception as e:
retries += 1
error_log_msg = str(e)
logger.warning(
f"Streaming API call failed with error: {str(e)}. Attempt {retries} of {max_retries}"
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
)
# 解析错误信息并记录到数据库
error_code = None
match = re.search(r"status code (\d+)", error_log_msg)
if match:
error_code = int(match.group(1))
await add_error_log(
gemini_key=api_key,
model_name=model,
error_log=error_log_msg,
error_code=error_code,
request_msg=payload
)
# 尝试切换 API Key
api_key = await self.key_manager.handle_api_failure(api_key)
logger.info(f"Switched to new API key: {api_key}")
if retries >= max_retries:

View File

@@ -1,6 +1,7 @@
# app/services/chat_service.py
import json
import re
from copy import deepcopy
from typing import Any, AsyncGenerator, Dict, List, Optional, Union
@@ -13,6 +14,7 @@ from app.log.logger import get_openai_logger
from app.service.client.api_client import GeminiApiClient
from app.service.image.image_create_service import ImageCreateService
from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log
logger = get_openai_logger()
@@ -189,10 +191,28 @@ class OpenAIChatService:
self, model: str, payload: Dict[str, Any], api_key: str
) -> Dict[str, Any]:
"""处理普通聊天完成"""
response = await self.api_client.generate_content(payload, model, api_key)
return self.response_handler.handle_response(
response, model, stream=False, finish_reason="stop"
)
try:
response = await self.api_client.generate_content(payload, model, api_key)
return self.response_handler.handle_response(
response, model, stream=False, finish_reason="stop"
)
except Exception as e:
logger.error(f"Normal API call failed with error: {str(e)}")
error_code = None
error_log_msg = str(e)
# 尝试从异常消息中解析状态码
match = re.search(r"status code (\d+)", error_log_msg)
if match:
error_code = int(match.group(1))
await add_error_log(
gemini_key=api_key,
model_name=model,
error_log=error_log_msg,
error_code=error_code,
request_msg=payload
)
raise e # 重新抛出异常,以便上层处理
async def _handle_stream_completion(
self, model: str, payload: Dict[str, Any], api_key: str
@@ -241,9 +261,26 @@ class OpenAIChatService:
break # 成功后退出循环
except Exception as e:
retries += 1
error_log_msg = str(e)
logger.warning(
f"Streaming API call failed with error: {str(e)}. Attempt {retries} of {max_retries}"
f"Streaming API call failed with error: {error_log_msg}. Attempt {retries} of {max_retries}"
)
# 解析错误信息并记录到数据库
error_code = None
match = re.search(r"status code (\d+)", error_log_msg)
if match:
error_code = int(match.group(1))
print(model)
await add_error_log(
gemini_key=api_key,
model_name=model,
error_type="openai_chat_service",
error_log=error_log_msg,
error_code=error_code,
request_msg=payload
)
# 尝试切换 API Key
api_key = await self.key_manager.handle_api_failure(api_key)
logger.info(f"Switched to new API key: {api_key}")
if retries >= max_retries:

View File

@@ -109,6 +109,7 @@ function renderErrorLogs() {
<td>${log.id}</td>
<td>${log.gemini_key || '无'}</td>
<td class="error-log-content">${errorLogContent}</td>
<td>${log.model_name || '未知'}</td>
<td>${formattedTime}</td>
<td>
<button class="btn btn-sm btn-primary btn-view-details" data-log-id="${log.id}">
@@ -168,6 +169,8 @@ function showLogDetails(logId) {
document.getElementById('modalGeminiKey').textContent = log.gemini_key || '无';
document.getElementById('modalErrorLog').textContent = log.error_log || '无';
document.getElementById('modalRequestMsg').textContent = formattedRequestMsg;
// Add model name display logic here - assuming an element with id 'modalModelName' exists
document.getElementById('modalModelName').textContent = log.model_name || '未知';
document.getElementById('modalRequestTime').textContent = formattedTime;
// 显示模态框

View File

@@ -64,6 +64,7 @@
<th>ID</th>
<th>Gemini密钥</th>
<th>错误日志</th>
<th>模型名称</th>
<th>请求时间</th>
<th>操作</th>
</tr>
@@ -138,6 +139,10 @@
<h6>请求消息:</h6>
<pre id="modalRequestMsg" class="bg-light p-2 rounded"></pre>
</div>
<div class="mb-3">
<h6>模型名称:</h6>
<p id="modalModelName"></p>
</div>
<div>
<h6>请求时间:</h6>
<p id="modalRequestTime"></p>