mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 08:12:26 +08:00
Initial commit
This commit is contained in:
675
src/core/exceptions.py
Normal file
675
src/core/exceptions.py
Normal file
@@ -0,0 +1,675 @@
|
||||
"""
|
||||
统一的异常处理和错误响应定义
|
||||
|
||||
安全说明:
|
||||
- 生产环境不返回详细错误信息,避免信息泄露
|
||||
- 使用错误 ID 关联日志,便于排查问题
|
||||
- 开发环境可返回详细信息用于调试
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
import traceback
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from ..config import config
|
||||
from src.core.logger import logger
|
||||
|
||||
|
||||
# Pydantic 错误消息中英文翻译映射
|
||||
PYDANTIC_ERROR_TRANSLATIONS = {
|
||||
# 字符串验证
|
||||
r"String should have at least (\d+) characters?": r"字符串长度至少需要 \1 个字符",
|
||||
r"String should have at most (\d+) characters?": r"字符串长度最多 \1 个字符",
|
||||
r"string_too_short": "字符串长度不足",
|
||||
r"string_too_long": "字符串长度超出限制",
|
||||
# 必填字段
|
||||
r"Field required": "此字段为必填项",
|
||||
r"field required": "此字段为必填项",
|
||||
r"missing": "缺少必填字段",
|
||||
# 类型错误
|
||||
r"Input should be a valid string": "输入应为有效的字符串",
|
||||
r"Input should be a valid integer": "输入应为有效的整数",
|
||||
r"Input should be a valid number": "输入应为有效的数字",
|
||||
r"Input should be a valid boolean": "输入应为布尔值",
|
||||
r"Input should be a valid email address": "输入应为有效的邮箱地址",
|
||||
r"Input should be a valid list": "输入应为有效的列表",
|
||||
r"Input should be a valid dictionary": "输入应为有效的字典",
|
||||
# 数值验证
|
||||
r"Input should be greater than (\d+)": r"数值应大于 \1",
|
||||
r"Input should be greater than or equal to (\d+)": r"数值应大于或等于 \1",
|
||||
r"Input should be less than (\d+)": r"数值应小于 \1",
|
||||
r"Input should be less than or equal to (\d+)": r"数值应小于或等于 \1",
|
||||
# 枚举验证
|
||||
r"Input should be (.+)": r"输入应为 \1",
|
||||
# 其他
|
||||
r"value is not a valid email address": "邮箱地址格式无效",
|
||||
r"invalid.*email": "邮箱地址格式无效",
|
||||
r"Extra inputs are not permitted": "不允许额外的字段",
|
||||
r"Value error, (.+)": r"\1", # 自定义验证器的错误直接使用
|
||||
}
|
||||
|
||||
# 字段名中英文翻译映射
|
||||
FIELD_NAME_TRANSLATIONS = {
|
||||
"password": "密码",
|
||||
"username": "用户名",
|
||||
"email": "邮箱",
|
||||
"role": "角色",
|
||||
"quota_usd": "配额",
|
||||
"name": "名称",
|
||||
"title": "标题",
|
||||
"content": "内容",
|
||||
"ip_address": "IP地址",
|
||||
"reason": "原因",
|
||||
"ttl": "过期时间",
|
||||
"enabled": "启用状态",
|
||||
"fixed_limit": "固定限制",
|
||||
"old_password": "旧密码",
|
||||
"new_password": "新密码",
|
||||
"allowed_providers": "允许的提供商",
|
||||
"allowed_models": "允许的模型",
|
||||
"rate_limit": "速率限制",
|
||||
"expire_days": "过期天数",
|
||||
"priority": "优先级",
|
||||
"type": "类型",
|
||||
"is_active": "激活状态",
|
||||
"is_pinned": "置顶状态",
|
||||
"start_time": "开始时间",
|
||||
"end_time": "结束时间",
|
||||
}
|
||||
|
||||
|
||||
def translate_pydantic_error(error: Dict[str, Any]) -> str:
|
||||
"""
|
||||
将 Pydantic 验证错误翻译为中文
|
||||
|
||||
Args:
|
||||
error: Pydantic 错误字典,包含 loc, msg, type 等字段
|
||||
|
||||
Returns:
|
||||
翻译后的中文错误消息
|
||||
"""
|
||||
# 获取字段名
|
||||
loc = error.get("loc", [])
|
||||
field = str(loc[0]) if loc else ""
|
||||
field_zh = FIELD_NAME_TRANSLATIONS.get(field, field)
|
||||
|
||||
# 获取错误消息
|
||||
msg = error.get("msg", "验证失败")
|
||||
|
||||
# 尝试翻译错误消息
|
||||
translated_msg = msg
|
||||
for pattern, replacement in PYDANTIC_ERROR_TRANSLATIONS.items():
|
||||
if re.search(pattern, msg, re.IGNORECASE):
|
||||
translated_msg = re.sub(pattern, replacement, msg, flags=re.IGNORECASE)
|
||||
break
|
||||
|
||||
# 组合字段名和错误消息
|
||||
if field_zh:
|
||||
return f"{field_zh}: {translated_msg}"
|
||||
return translated_msg
|
||||
|
||||
|
||||
def translate_pydantic_errors(errors: List[Dict[str, Any]]) -> str:
|
||||
"""
|
||||
翻译多个 Pydantic 验证错误
|
||||
|
||||
Args:
|
||||
errors: Pydantic 错误列表
|
||||
|
||||
Returns:
|
||||
翻译后的错误消息,多个错误用分号分隔
|
||||
"""
|
||||
if not errors:
|
||||
return "请求数据验证失败"
|
||||
|
||||
translated = [translate_pydantic_error(e) for e in errors]
|
||||
return "; ".join(translated)
|
||||
|
||||
|
||||
|
||||
# 延迟导入韧性管理器,避免循环导入
|
||||
def get_resilience_manager():
|
||||
try:
|
||||
from ..core.resilience import resilience_manager
|
||||
|
||||
return resilience_manager
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
class ProxyException(HTTPException):
|
||||
"""代理服务基础异常"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status_code: int,
|
||||
error_type: str,
|
||||
message: str,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
self.error_type = error_type
|
||||
self.message = message
|
||||
self.details = details or {}
|
||||
super().__init__(status_code=status_code, detail=message)
|
||||
|
||||
|
||||
class ProviderException(ProxyException):
|
||||
"""提供商相关异常"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
provider_name: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.request_metadata = request_metadata # 保存元数据以便传递
|
||||
details = {"provider": provider_name} if provider_name else {}
|
||||
details.update(kwargs)
|
||||
super().__init__(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
error_type="provider_error",
|
||||
message=message,
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class ProviderNotAvailableException(ProviderException):
|
||||
"""提供商不可用"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
provider_name: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=message,
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
)
|
||||
|
||||
|
||||
class ProviderTimeoutException(ProviderException):
|
||||
"""提供商请求超时"""
|
||||
|
||||
def __init__(self, provider_name: str, timeout: int, request_metadata: Optional[Any] = None):
|
||||
super().__init__(
|
||||
message=f"提供商 '{provider_name}' 请求超时({timeout}秒)",
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
||||
class ProviderAuthException(ProviderException):
|
||||
"""提供商认证失败"""
|
||||
|
||||
def __init__(self, provider_name: str, request_metadata: Optional[Any] = None):
|
||||
super().__init__(
|
||||
message=f"提供商 '{provider_name}' 认证失败,请检查API密钥",
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
)
|
||||
|
||||
|
||||
class ProviderRateLimitException(ProviderException):
|
||||
"""提供商限流"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
provider_name: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
response_headers: Optional[Dict[str, str]] = None, # 添加响应头
|
||||
retry_after: Optional[int] = None, # 添加重试时间
|
||||
):
|
||||
self.response_headers = response_headers or {} # 保存响应头
|
||||
self.retry_after = retry_after # 保存重试时间
|
||||
super().__init__(
|
||||
message=message, provider_name=provider_name, request_metadata=request_metadata
|
||||
)
|
||||
|
||||
|
||||
class QuotaExceededException(ProxyException):
|
||||
"""配额超限"""
|
||||
|
||||
def __init__(self, quota_type: str = "tokens", remaining: Optional[float] = None):
|
||||
message = f"{quota_type}配额已用尽"
|
||||
if remaining is not None:
|
||||
message += f"(剩余: {remaining})"
|
||||
super().__init__(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
error_type="quota_exceeded",
|
||||
message=message,
|
||||
details={"quota_type": quota_type, "remaining": remaining},
|
||||
)
|
||||
|
||||
|
||||
class RateLimitException(ProxyException):
|
||||
"""速率限制"""
|
||||
|
||||
def __init__(self, limit: int, window: str = "minute"):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
error_type="rate_limit",
|
||||
message=f"请求过于频繁,限制为每{window} {limit}次",
|
||||
details={"limit": limit, "window": window},
|
||||
)
|
||||
|
||||
|
||||
class ConcurrencyLimitError(ProxyException):
|
||||
"""并发限制异常"""
|
||||
|
||||
def __init__(
|
||||
self, message: str, endpoint_id: Optional[str] = None, key_id: Optional[str] = None
|
||||
):
|
||||
details = {}
|
||||
if endpoint_id:
|
||||
details["endpoint_id"] = endpoint_id
|
||||
if key_id:
|
||||
details["key_id"] = key_id
|
||||
|
||||
super().__init__(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
error_type="concurrency_limit",
|
||||
message=message,
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class ModelNotSupportedException(ProxyException):
|
||||
"""模型不支持"""
|
||||
|
||||
def __init__(self, model: str, provider_name: Optional[str] = None):
|
||||
message = f"模型 '{model}' 不受支持"
|
||||
if provider_name:
|
||||
message = f"提供商 '{provider_name}' 不支持模型 '{model}'"
|
||||
super().__init__(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
error_type="model_not_supported",
|
||||
message=message,
|
||||
details={"model": model, "provider": provider_name},
|
||||
)
|
||||
|
||||
|
||||
class StreamingNotSupportedException(ProxyException):
|
||||
"""流式请求不支持"""
|
||||
|
||||
def __init__(self, model: str, provider_name: Optional[str] = None):
|
||||
if provider_name:
|
||||
message = f"模型 '{model}' 在提供商 '{provider_name}' 上不支持流式请求"
|
||||
else:
|
||||
message = f"模型 '{model}' 不支持流式请求"
|
||||
super().__init__(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
error_type="streaming_not_supported",
|
||||
message=message,
|
||||
details={"model": model, "provider": provider_name},
|
||||
)
|
||||
|
||||
|
||||
class InvalidRequestException(ProxyException):
|
||||
"""无效请求"""
|
||||
|
||||
def __init__(self, message: str, field: Optional[str] = None):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
error_type="invalid_request",
|
||||
message=message,
|
||||
details={"field": field} if field else {},
|
||||
)
|
||||
|
||||
|
||||
class NotFoundException(ProxyException):
|
||||
"""资源未找到"""
|
||||
|
||||
def __init__(self, message: str, resource_type: Optional[str] = None):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
error_type="not_found",
|
||||
message=message,
|
||||
details={"resource_type": resource_type} if resource_type else {},
|
||||
)
|
||||
|
||||
|
||||
class ForbiddenException(ProxyException):
|
||||
"""权限不足"""
|
||||
|
||||
def __init__(self, message: str, required_role: Optional[str] = None):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
error_type="forbidden",
|
||||
message=message,
|
||||
details={"required_role": required_role} if required_role else {},
|
||||
)
|
||||
|
||||
|
||||
class DecryptionException(ProxyException):
|
||||
"""解密失败异常 - 已知的配置问题,不需要打印堆栈"""
|
||||
|
||||
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
error_type="decryption_error",
|
||||
message=message,
|
||||
details=details or {},
|
||||
)
|
||||
|
||||
|
||||
class JSONParseException(ProviderException):
|
||||
"""JSON解析错误"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider_name: str,
|
||||
original_error: str,
|
||||
response_content: Optional[str] = None,
|
||||
content_type: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
):
|
||||
details = {
|
||||
"original_error": original_error,
|
||||
"content_type": content_type,
|
||||
}
|
||||
if response_content and len(response_content) > 500:
|
||||
# 截断长内容,但保留头尾
|
||||
details["response_preview"] = f"{response_content[:200]}...{response_content[-200:]}"
|
||||
elif response_content:
|
||||
details["response_content"] = response_content
|
||||
|
||||
super().__init__(
|
||||
message=f"提供商 '{provider_name}' 返回了无效的JSON响应",
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
**details,
|
||||
)
|
||||
|
||||
|
||||
class EmptyStreamException(ProviderException):
|
||||
"""流式响应为空异常 - 上游返回200但没有发送任何数据"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider_name: str,
|
||||
chunk_count: int = 0,
|
||||
request_metadata: Optional[Any] = None,
|
||||
):
|
||||
super().__init__(
|
||||
message=f"提供商 '{provider_name}' 返回了空的流式响应(status=200 但无数据)",
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
chunk_count=chunk_count,
|
||||
)
|
||||
|
||||
|
||||
class EmbeddedErrorException(ProviderException):
|
||||
"""响应体内嵌套错误异常 - HTTP 状态码正常但响应体包含错误信息
|
||||
|
||||
用于处理某些 Provider(如 Gemini)返回 HTTP 200 但在响应体中包含错误的情况。
|
||||
这类错误需要触发重试逻辑。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
provider_name: str,
|
||||
error_code: Optional[int] = None,
|
||||
error_message: Optional[str] = None,
|
||||
error_status: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
):
|
||||
message = f"提供商 '{provider_name}' 返回了嵌套错误"
|
||||
if error_code:
|
||||
message += f" (code={error_code})"
|
||||
if error_message:
|
||||
message += f": {error_message}"
|
||||
|
||||
super().__init__(
|
||||
message=message,
|
||||
provider_name=provider_name,
|
||||
request_metadata=request_metadata,
|
||||
error_code=error_code,
|
||||
error_status=error_status,
|
||||
)
|
||||
self.error_code = error_code
|
||||
self.error_message = error_message
|
||||
self.error_status = error_status
|
||||
|
||||
|
||||
class UpstreamClientException(ProxyException):
|
||||
"""上游返回的客户端错误异常 - HTTP 4xx 错误,不应该重试
|
||||
|
||||
用于处理上游 Provider 返回的客户端错误(如图片处理失败、无效请求等)。
|
||||
这类错误是由用户请求本身的问题导致的,换 Provider 也无济于事,不应该重试。
|
||||
|
||||
常见场景:
|
||||
- 图片处理失败(图片过大、格式不支持等)
|
||||
- 请求参数无效
|
||||
- 消息内容违规
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
provider_name: Optional[str] = None,
|
||||
status_code: int = 400,
|
||||
error_type: Optional[str] = None,
|
||||
upstream_error: Optional[str] = None,
|
||||
request_metadata: Optional[Any] = None,
|
||||
):
|
||||
self.upstream_error = upstream_error
|
||||
self.request_metadata = request_metadata
|
||||
details = {}
|
||||
if provider_name:
|
||||
details["provider"] = provider_name
|
||||
if error_type:
|
||||
details["upstream_error_type"] = error_type
|
||||
if upstream_error:
|
||||
details["upstream_error"] = upstream_error
|
||||
|
||||
super().__init__(
|
||||
status_code=status_code,
|
||||
error_type="upstream_client_error",
|
||||
message=message,
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
class ErrorResponse:
|
||||
"""统一的错误响应格式化器"""
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
error_type: str,
|
||||
message: str,
|
||||
status_code: int = 500,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
) -> JSONResponse:
|
||||
"""创建标准错误响应"""
|
||||
error_body = {"error": {"type": error_type, "message": message}}
|
||||
|
||||
if details:
|
||||
error_body["error"]["details"] = details
|
||||
|
||||
# 记录错误日志
|
||||
logger.error(f"Error response: {error_type} - {message}")
|
||||
|
||||
return JSONResponse(status_code=status_code, content=error_body)
|
||||
|
||||
@staticmethod
|
||||
def from_exception(e: Exception) -> JSONResponse:
|
||||
"""
|
||||
从异常创建错误响应
|
||||
|
||||
安全说明:
|
||||
- 生产环境只返回错误 ID,不暴露详细信息
|
||||
- 开发环境返回完整错误信息用于调试
|
||||
- 所有错误都记录到日志,通过错误 ID 关联
|
||||
"""
|
||||
if isinstance(e, ProxyException):
|
||||
return ErrorResponse.create(
|
||||
error_type=e.error_type,
|
||||
message=e.message,
|
||||
status_code=e.status_code,
|
||||
details=e.details,
|
||||
)
|
||||
elif isinstance(e, HTTPException):
|
||||
return ErrorResponse.create(
|
||||
error_type="http_error", message=str(e.detail), status_code=e.status_code
|
||||
)
|
||||
else:
|
||||
# 未知异常,使用错误 ID 机制
|
||||
error_id = str(uuid.uuid4())[:8] # 短 ID,便于用户报告
|
||||
error_type_name = type(e).__name__
|
||||
error_message = str(e)
|
||||
|
||||
# 始终记录完整错误到日志
|
||||
logger.error(f"[{error_id}] Unexpected error: {error_type_name}: {error_message}")
|
||||
|
||||
# 根据环境决定返回的详细程度
|
||||
is_development = config.environment in ("development", "test", "testing")
|
||||
|
||||
if is_development:
|
||||
# 开发环境:返回完整错误信息
|
||||
return ErrorResponse.create(
|
||||
error_type="internal_error",
|
||||
message=f"内部服务器错误: {error_type_name}: {error_message}",
|
||||
status_code=500,
|
||||
details={
|
||||
"error_id": error_id,
|
||||
"error_type": error_type_name,
|
||||
"error": error_message,
|
||||
"traceback": traceback.format_exc().split("\n"),
|
||||
},
|
||||
)
|
||||
else:
|
||||
# 生产环境:只返回错误 ID
|
||||
return ErrorResponse.create(
|
||||
error_type="internal_error",
|
||||
message="内部服务器错误",
|
||||
status_code=500,
|
||||
details={
|
||||
"error_id": error_id,
|
||||
"support_info": "请联系管理员并提供此错误 ID",
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def provider_error(provider_name: str, error: Exception) -> JSONResponse:
|
||||
"""提供商错误响应 - 基于异常类型判断"""
|
||||
# 基于异常类型判断,更可靠
|
||||
if isinstance(error, (asyncio.TimeoutError, httpx.TimeoutException)):
|
||||
return ErrorResponse.from_exception(ProviderTimeoutException(provider_name, 60))
|
||||
elif isinstance(error, (httpx.HTTPStatusError,)):
|
||||
if error.response.status_code == 401:
|
||||
return ErrorResponse.from_exception(ProviderAuthException(provider_name))
|
||||
elif error.response.status_code == 429:
|
||||
return ErrorResponse.from_exception(
|
||||
ProviderRateLimitException(
|
||||
message=f"提供商 '{provider_name}' 速率限制",
|
||||
provider_name=provider_name,
|
||||
)
|
||||
)
|
||||
elif isinstance(error, (httpx.ConnectError, httpx.NetworkError)):
|
||||
return ErrorResponse.create(
|
||||
error_type="provider_connection_error",
|
||||
message=f"无法连接到提供商 {provider_name}",
|
||||
status_code=503,
|
||||
details={"provider": provider_name, "error": "Connection failed"},
|
||||
)
|
||||
# 如果异常类型无法判断,再通过字符串匹配作为备用
|
||||
elif "auth" in str(error).lower() or "401" in str(error):
|
||||
return ErrorResponse.from_exception(ProviderAuthException(provider_name))
|
||||
elif "rate limit" in str(error).lower() or "429" in str(error):
|
||||
return ErrorResponse.from_exception(
|
||||
ProviderRateLimitException(
|
||||
message=f"提供商 '{provider_name}' 速率限制",
|
||||
provider_name=provider_name,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return ErrorResponse.create(
|
||||
error_type="provider_error",
|
||||
message=f"提供商请求失败: {str(error)}",
|
||||
status_code=503,
|
||||
details={
|
||||
"provider": provider_name,
|
||||
"error": str(error),
|
||||
"error_type": type(error).__name__,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ExceptionHandlers:
|
||||
"""FastAPI异常处理器"""
|
||||
|
||||
@staticmethod
|
||||
async def handle_proxy_exception(request, exc: ProxyException):
|
||||
"""处理代理异常"""
|
||||
return ErrorResponse.from_exception(exc)
|
||||
|
||||
@staticmethod
|
||||
async def handle_http_exception(request, exc: HTTPException):
|
||||
"""处理HTTP异常"""
|
||||
return ErrorResponse.from_exception(exc)
|
||||
|
||||
@staticmethod
|
||||
async def handle_generic_exception(request, exc: Exception):
|
||||
"""处理通用异常 - 集成韧性管理"""
|
||||
|
||||
# 首先检查是否为HTTPException,如果是则委托给HTTP异常处理器
|
||||
if isinstance(exc, HTTPException):
|
||||
return await ExceptionHandlers.handle_http_exception(request, exc)
|
||||
|
||||
# 获取请求信息用于上下文
|
||||
request_info = {
|
||||
"method": request.method,
|
||||
"url": str(request.url),
|
||||
"client_ip": (
|
||||
getattr(request.client, "host", "unknown")
|
||||
if hasattr(request, "client")
|
||||
else "unknown"
|
||||
),
|
||||
"user_agent": request.headers.get("user-agent", "unknown"),
|
||||
}
|
||||
|
||||
# 使用韧性管理器处理错误
|
||||
rm = get_resilience_manager()
|
||||
if rm:
|
||||
try:
|
||||
error_result = rm.handle_error(
|
||||
error=exc,
|
||||
context=request_info,
|
||||
operation=f"{request.method} {request.url.path}",
|
||||
)
|
||||
|
||||
# 根据错误处理结果返回适当的响应
|
||||
if error_result.get("severity") and error_result["severity"].value == "critical":
|
||||
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
elif error_result.get("severity") and error_result["severity"].value == "high":
|
||||
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
else:
|
||||
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
|
||||
return ErrorResponse.create(
|
||||
status_code=status_code,
|
||||
error_type="system_error",
|
||||
message=error_result.get("user_message", "系统遇到未知错误"),
|
||||
details={
|
||||
"error_id": error_result.get("error_id"),
|
||||
"recovery_info": "请稍后重试,如问题持续请联系管理员",
|
||||
},
|
||||
)
|
||||
|
||||
except Exception as resilience_error:
|
||||
# 如果韧性管理器本身出错,降级到基本处理
|
||||
logger.exception("韧性管理器处理异常时出错")
|
||||
|
||||
# 降级处理:基本的异常响应
|
||||
return ErrorResponse.from_exception(exc)
|
||||
Reference in New Issue
Block a user