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
+3 -2
View File
@@ -22,8 +22,9 @@ metadata = MetaData()
# 创建基类 # 创建基类
Base = declarative_base(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(): async def connect_to_db():
+3
View File
@@ -32,7 +32,10 @@ class ErrorLog(Base):
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
gemini_key = Column(String(100), nullable=True, comment="Gemini API密钥") 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_log = Column(Text, nullable=True, comment="错误日志")
error_code = Column(Integer, nullable=True, comment="错误代码")
request_msg = Column(JSON, nullable=True, comment="请求消息") request_msg = Column(JSON, nullable=True, comment="请求消息")
request_time = Column(DateTime, default=datetime.datetime.now, comment="请求时间") request_time = Column(DateTime, default=datetime.datetime.now, comment="请求时间")
+7
View File
@@ -101,7 +101,10 @@ async def update_setting(key: str, value: str, description: Optional[str] = None
async def add_error_log( async def add_error_log(
gemini_key: Optional[str] = None, gemini_key: Optional[str] = None,
model_name: Optional[str] = None,
error_type: Optional[str] = None,
error_log: Optional[str] = None, error_log: Optional[str] = None,
error_code: Optional[int] = None,
request_msg: Optional[Union[Dict[str, Any], str]] = None request_msg: Optional[Union[Dict[str, Any], str]] = None
) -> bool: ) -> bool:
""" """
@@ -110,6 +113,7 @@ async def add_error_log(
Args: Args:
gemini_key: Gemini API密钥 gemini_key: Gemini API密钥
error_log: 错误日志 error_log: 错误日志
error_code: 错误代码 (例如 HTTP 状态码)
request_msg: 请求消息 request_msg: 请求消息
Returns: Returns:
@@ -132,7 +136,10 @@ async def add_error_log(
insert(ErrorLog) insert(ErrorLog)
.values( .values(
gemini_key=gemini_key, gemini_key=gemini_key,
error_type=error_type,
error_log=error_log, error_log=error_log,
model_name=model_name,
error_code=error_code,
request_msg=request_msg_json, request_msg=request_msg_json,
request_time=datetime.datetime.now() request_time=datetime.datetime.now()
) )
+40 -3
View File
@@ -1,6 +1,7 @@
# app/services/chat_service.py # app/services/chat_service.py
import json import json
import re
from typing import Any, AsyncGenerator, Dict, List from typing import Any, AsyncGenerator, Dict, List
from app.config.config import settings 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.log.logger import get_gemini_logger
from app.service.client.api_client import GeminiApiClient from app.service.client.api_client import GeminiApiClient
from app.service.key.key_manager import KeyManager from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log
logger = get_gemini_logger() logger = get_gemini_logger()
@@ -145,8 +147,27 @@ class GeminiChatService:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""生成内容""" """生成内容"""
payload = _build_payload(model, request) payload = _build_payload(model, request)
response = await self.api_client.generate_content(payload, model, api_key) try:
return self.response_handler.handle_response(response, model, stream=False) 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( async def stream_generate_content(
self, model: str, request: GeminiRequest, api_key: str self, model: str, request: GeminiRequest, api_key: str
@@ -185,9 +206,25 @@ class GeminiChatService:
break break
except Exception as e: except Exception as e:
retries += 1 retries += 1
error_log_msg = str(e)
logger.warning( 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) api_key = await self.key_manager.handle_api_failure(api_key)
logger.info(f"Switched to new API key: {api_key}") logger.info(f"Switched to new API key: {api_key}")
if retries >= max_retries: if retries >= max_retries:
+42 -5
View File
@@ -1,6 +1,7 @@
# app/services/chat_service.py # app/services/chat_service.py
import json import json
import re
from copy import deepcopy from copy import deepcopy
from typing import Any, AsyncGenerator, Dict, List, Optional, Union 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.client.api_client import GeminiApiClient
from app.service.image.image_create_service import ImageCreateService from app.service.image.image_create_service import ImageCreateService
from app.service.key.key_manager import KeyManager from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log
logger = get_openai_logger() logger = get_openai_logger()
@@ -189,10 +191,28 @@ class OpenAIChatService:
self, model: str, payload: Dict[str, Any], api_key: str self, model: str, payload: Dict[str, Any], api_key: str
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""处理普通聊天完成""" """处理普通聊天完成"""
response = await self.api_client.generate_content(payload, model, api_key) try:
return self.response_handler.handle_response( response = await self.api_client.generate_content(payload, model, api_key)
response, model, stream=False, finish_reason="stop" 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( async def _handle_stream_completion(
self, model: str, payload: Dict[str, Any], api_key: str self, model: str, payload: Dict[str, Any], api_key: str
@@ -241,9 +261,26 @@ class OpenAIChatService:
break # 成功后退出循环 break # 成功后退出循环
except Exception as e: except Exception as e:
retries += 1 retries += 1
error_log_msg = str(e)
logger.warning( 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) api_key = await self.key_manager.handle_api_failure(api_key)
logger.info(f"Switched to new API key: {api_key}") logger.info(f"Switched to new API key: {api_key}")
if retries >= max_retries: if retries >= max_retries:
+3
View File
@@ -109,6 +109,7 @@ function renderErrorLogs() {
<td>${log.id}</td> <td>${log.id}</td>
<td>${log.gemini_key || '无'}</td> <td>${log.gemini_key || '无'}</td>
<td class="error-log-content">${errorLogContent}</td> <td class="error-log-content">${errorLogContent}</td>
<td>${log.model_name || '未知'}</td>
<td>${formattedTime}</td> <td>${formattedTime}</td>
<td> <td>
<button class="btn btn-sm btn-primary btn-view-details" data-log-id="${log.id}"> <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('modalGeminiKey').textContent = log.gemini_key || '无';
document.getElementById('modalErrorLog').textContent = log.error_log || '无'; document.getElementById('modalErrorLog').textContent = log.error_log || '无';
document.getElementById('modalRequestMsg').textContent = formattedRequestMsg; 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; document.getElementById('modalRequestTime').textContent = formattedTime;
// 显示模态框 // 显示模态框
+5
View File
@@ -64,6 +64,7 @@
<th>ID</th> <th>ID</th>
<th>Gemini密钥</th> <th>Gemini密钥</th>
<th>错误日志</th> <th>错误日志</th>
<th>模型名称</th>
<th>请求时间</th> <th>请求时间</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
@@ -138,6 +139,10 @@
<h6>请求消息:</h6> <h6>请求消息:</h6>
<pre id="modalRequestMsg" class="bg-light p-2 rounded"></pre> <pre id="modalRequestMsg" class="bg-light p-2 rounded"></pre>
</div> </div>
<div class="mb-3">
<h6>模型名称:</h6>
<p id="modalModelName"></p>
</div>
<div> <div>
<h6>请求时间:</h6> <h6>请求时间:</h6>
<p id="modalRequestTime"></p> <p id="modalRequestTime"></p>