mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-06 16:16:37 +08:00
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:
@@ -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():
|
||||||
|
|||||||
@@ -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="请求时间")
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
// 显示模态框
|
// 显示模态框
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user