Merge branch 'main' of https://gitea.functen.cn/shihao/WechatHookBot
This commit is contained in:
438
utils/errors.py
Normal file
438
utils/errors.py
Normal file
@@ -0,0 +1,438 @@
|
||||
"""
|
||||
统一错误处理模块
|
||||
|
||||
提供:
|
||||
- 自定义异常类层次结构
|
||||
- 错误包装和转换
|
||||
- 用户友好的错误消息
|
||||
- 错误日志和追踪
|
||||
|
||||
使用示例:
|
||||
from utils.errors import PluginError, ToolExecutionError, handle_error
|
||||
|
||||
try:
|
||||
await some_operation()
|
||||
except Exception as e:
|
||||
result = handle_error(e, context="执行工具")
|
||||
# result = {"success": False, "error": "...", "error_type": "..."}
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional, Type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
# ==================== 错误类型枚举 ====================
|
||||
|
||||
class ErrorType(Enum):
|
||||
"""错误类型分类"""
|
||||
UNKNOWN = "unknown"
|
||||
PLUGIN = "plugin"
|
||||
TOOL = "tool"
|
||||
CONFIG = "config"
|
||||
NETWORK = "network"
|
||||
TIMEOUT = "timeout"
|
||||
VALIDATION = "validation"
|
||||
PERMISSION = "permission"
|
||||
RESOURCE = "resource"
|
||||
|
||||
|
||||
# ==================== 自定义异常基类 ====================
|
||||
|
||||
class BotError(Exception):
|
||||
"""机器人错误基类"""
|
||||
|
||||
error_type: ErrorType = ErrorType.UNKNOWN
|
||||
user_message: str = "发生了一个错误"
|
||||
log_level: str = "error"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
user_message: str = None,
|
||||
cause: Exception = None,
|
||||
context: Dict[str, Any] = None,
|
||||
):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self._user_message = user_message
|
||||
self.cause = cause
|
||||
self.context = context or {}
|
||||
|
||||
def get_user_message(self) -> str:
|
||||
"""获取用户友好的错误消息"""
|
||||
return self._user_message or self.user_message
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典(用于 API 响应)"""
|
||||
return {
|
||||
"success": False,
|
||||
"error": self.get_user_message(),
|
||||
"error_type": self.error_type.value,
|
||||
"details": self.message if self.message != self.get_user_message() else None,
|
||||
}
|
||||
|
||||
|
||||
# ==================== 具体异常类 ====================
|
||||
|
||||
class PluginError(BotError):
|
||||
"""插件相关错误"""
|
||||
error_type = ErrorType.PLUGIN
|
||||
user_message = "插件执行出错"
|
||||
|
||||
|
||||
class PluginLoadError(PluginError):
|
||||
"""插件加载错误"""
|
||||
user_message = "插件加载失败"
|
||||
|
||||
|
||||
class PluginNotFoundError(PluginError):
|
||||
"""插件未找到"""
|
||||
user_message = "找不到指定的插件"
|
||||
|
||||
|
||||
class ToolExecutionError(BotError):
|
||||
"""工具执行错误"""
|
||||
error_type = ErrorType.TOOL
|
||||
user_message = "工具执行失败"
|
||||
|
||||
|
||||
class ToolNotFoundError(ToolExecutionError):
|
||||
"""工具未找到"""
|
||||
user_message = "找不到指定的工具"
|
||||
|
||||
|
||||
class ToolTimeoutError(ToolExecutionError):
|
||||
"""工具执行超时"""
|
||||
error_type = ErrorType.TIMEOUT
|
||||
user_message = "工具执行超时"
|
||||
|
||||
|
||||
class ConfigError(BotError):
|
||||
"""配置相关错误"""
|
||||
error_type = ErrorType.CONFIG
|
||||
user_message = "配置错误"
|
||||
|
||||
|
||||
class ConfigNotFoundError(ConfigError):
|
||||
"""配置项未找到"""
|
||||
user_message = "找不到配置项"
|
||||
|
||||
|
||||
class ConfigValidationError(ConfigError):
|
||||
"""配置验证错误"""
|
||||
error_type = ErrorType.VALIDATION
|
||||
user_message = "配置格式不正确"
|
||||
|
||||
|
||||
class NetworkError(BotError):
|
||||
"""网络相关错误"""
|
||||
error_type = ErrorType.NETWORK
|
||||
user_message = "网络请求失败"
|
||||
|
||||
|
||||
class APIError(NetworkError):
|
||||
"""API 调用错误"""
|
||||
user_message = "API 调用失败"
|
||||
|
||||
|
||||
class ValidationError(BotError):
|
||||
"""验证错误"""
|
||||
error_type = ErrorType.VALIDATION
|
||||
user_message = "参数验证失败"
|
||||
|
||||
|
||||
class PermissionError(BotError):
|
||||
"""权限错误"""
|
||||
error_type = ErrorType.PERMISSION
|
||||
user_message = "没有权限执行此操作"
|
||||
|
||||
|
||||
class ResourceError(BotError):
|
||||
"""资源错误(内存、文件等)"""
|
||||
error_type = ErrorType.RESOURCE
|
||||
user_message = "资源访问失败"
|
||||
|
||||
|
||||
# ==================== 错误处理工具函数 ====================
|
||||
|
||||
@dataclass
|
||||
class ErrorResult:
|
||||
"""错误处理结果"""
|
||||
success: bool = False
|
||||
error: str = ""
|
||||
error_type: str = "unknown"
|
||||
details: Optional[str] = None
|
||||
logged: bool = False
|
||||
original_exception: Optional[Exception] = field(default=None, repr=False)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为字典"""
|
||||
result = {
|
||||
"success": self.success,
|
||||
"error": self.error,
|
||||
"error_type": self.error_type,
|
||||
}
|
||||
if self.details:
|
||||
result["details"] = self.details
|
||||
return result
|
||||
|
||||
|
||||
def handle_error(
|
||||
exception: Exception,
|
||||
context: str = "",
|
||||
log: bool = True,
|
||||
include_traceback: bool = False,
|
||||
) -> ErrorResult:
|
||||
"""
|
||||
统一错误处理函数
|
||||
|
||||
Args:
|
||||
exception: 捕获的异常
|
||||
context: 错误上下文描述
|
||||
log: 是否记录日志
|
||||
include_traceback: 是否包含完整堆栈
|
||||
|
||||
Returns:
|
||||
ErrorResult 对象
|
||||
"""
|
||||
# 处理自定义异常
|
||||
if isinstance(exception, BotError):
|
||||
result = ErrorResult(
|
||||
success=False,
|
||||
error=exception.get_user_message(),
|
||||
error_type=exception.error_type.value,
|
||||
details=exception.message if exception.message != exception.get_user_message() else None,
|
||||
original_exception=exception,
|
||||
)
|
||||
if log:
|
||||
log_func = getattr(logger, exception.log_level, logger.error)
|
||||
log_func(f"[{context}] {exception.error_type.value}: {exception.message}")
|
||||
result.logged = True
|
||||
return result
|
||||
|
||||
# 处理标准超时异常
|
||||
import asyncio
|
||||
if isinstance(exception, asyncio.TimeoutError):
|
||||
result = ErrorResult(
|
||||
success=False,
|
||||
error="操作超时",
|
||||
error_type=ErrorType.TIMEOUT.value,
|
||||
original_exception=exception,
|
||||
)
|
||||
if log:
|
||||
logger.warning(f"[{context}] 超时: {exception}")
|
||||
result.logged = True
|
||||
return result
|
||||
|
||||
# 处理连接错误
|
||||
if isinstance(exception, (ConnectionError, OSError)):
|
||||
result = ErrorResult(
|
||||
success=False,
|
||||
error="网络连接失败",
|
||||
error_type=ErrorType.NETWORK.value,
|
||||
details=str(exception),
|
||||
original_exception=exception,
|
||||
)
|
||||
if log:
|
||||
logger.error(f"[{context}] 网络错误: {exception}")
|
||||
result.logged = True
|
||||
return result
|
||||
|
||||
# 处理验证错误
|
||||
if isinstance(exception, (ValueError, TypeError)):
|
||||
result = ErrorResult(
|
||||
success=False,
|
||||
error="参数错误",
|
||||
error_type=ErrorType.VALIDATION.value,
|
||||
details=str(exception),
|
||||
original_exception=exception,
|
||||
)
|
||||
if log:
|
||||
logger.warning(f"[{context}] 验证错误: {exception}")
|
||||
result.logged = True
|
||||
return result
|
||||
|
||||
# 处理未知错误
|
||||
error_msg = str(exception) or exception.__class__.__name__
|
||||
details = None
|
||||
if include_traceback:
|
||||
details = traceback.format_exc()
|
||||
|
||||
result = ErrorResult(
|
||||
success=False,
|
||||
error=f"发生错误: {error_msg[:100]}",
|
||||
error_type=ErrorType.UNKNOWN.value,
|
||||
details=details,
|
||||
original_exception=exception,
|
||||
)
|
||||
|
||||
if log:
|
||||
logger.error(f"[{context}] 未知错误: {exception}")
|
||||
if include_traceback:
|
||||
logger.debug(traceback.format_exc())
|
||||
result.logged = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def wrap_error(
|
||||
exception: Exception,
|
||||
error_class: Type[BotError],
|
||||
message: str = None,
|
||||
user_message: str = None,
|
||||
) -> BotError:
|
||||
"""
|
||||
将标准异常包装为自定义异常
|
||||
|
||||
Args:
|
||||
exception: 原始异常
|
||||
error_class: 目标异常类
|
||||
message: 错误消息
|
||||
user_message: 用户友好消息
|
||||
|
||||
Returns:
|
||||
包装后的 BotError 子类实例
|
||||
"""
|
||||
msg = message or str(exception)
|
||||
return error_class(
|
||||
message=msg,
|
||||
user_message=user_message,
|
||||
cause=exception,
|
||||
)
|
||||
|
||||
|
||||
def safe_error_message(exception: Exception, max_length: int = 200) -> str:
|
||||
"""
|
||||
获取安全的错误消息(截断过长内容,移除敏感信息)
|
||||
|
||||
Args:
|
||||
exception: 异常对象
|
||||
max_length: 最大长度
|
||||
|
||||
Returns:
|
||||
安全的错误消息字符串
|
||||
"""
|
||||
msg = str(exception)
|
||||
|
||||
# 移除可能的敏感信息模式
|
||||
sensitive_patterns = [
|
||||
r'api[_-]?key[=:]\s*\S+',
|
||||
r'password[=:]\s*\S+',
|
||||
r'token[=:]\s*\S+',
|
||||
r'secret[=:]\s*\S+',
|
||||
]
|
||||
|
||||
import re
|
||||
for pattern in sensitive_patterns:
|
||||
msg = re.sub(pattern, '[REDACTED]', msg, flags=re.IGNORECASE)
|
||||
|
||||
# 截断
|
||||
if len(msg) > max_length:
|
||||
msg = msg[:max_length] + "..."
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
# ==================== 装饰器 ====================
|
||||
|
||||
def catch_errors(
|
||||
error_class: Type[BotError] = BotError,
|
||||
context: str = "",
|
||||
log: bool = True,
|
||||
reraise: bool = False,
|
||||
):
|
||||
"""
|
||||
错误捕获装饰器
|
||||
|
||||
Args:
|
||||
error_class: 转换为的错误类
|
||||
context: 上下文描述
|
||||
log: 是否记录日志
|
||||
reraise: 是否重新抛出
|
||||
|
||||
Usage:
|
||||
@catch_errors(ToolExecutionError, context="执行工具")
|
||||
async def my_tool():
|
||||
...
|
||||
"""
|
||||
def decorator(func):
|
||||
import asyncio
|
||||
import functools
|
||||
|
||||
@functools.wraps(func)
|
||||
async def async_wrapper(*args, **kwargs):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except BotError:
|
||||
if reraise:
|
||||
raise
|
||||
return None
|
||||
except Exception as e:
|
||||
ctx = context or func.__name__
|
||||
handle_error(e, context=ctx, log=log)
|
||||
if reraise:
|
||||
raise wrap_error(e, error_class) from e
|
||||
return None
|
||||
|
||||
@functools.wraps(func)
|
||||
def sync_wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except BotError:
|
||||
if reraise:
|
||||
raise
|
||||
return None
|
||||
except Exception as e:
|
||||
ctx = context or func.__name__
|
||||
handle_error(e, context=ctx, log=log)
|
||||
if reraise:
|
||||
raise wrap_error(e, error_class) from e
|
||||
return None
|
||||
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
return async_wrapper
|
||||
return sync_wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# ==================== 导出列表 ====================
|
||||
|
||||
__all__ = [
|
||||
# 枚举
|
||||
'ErrorType',
|
||||
# 异常基类
|
||||
'BotError',
|
||||
# 插件异常
|
||||
'PluginError',
|
||||
'PluginLoadError',
|
||||
'PluginNotFoundError',
|
||||
# 工具异常
|
||||
'ToolExecutionError',
|
||||
'ToolNotFoundError',
|
||||
'ToolTimeoutError',
|
||||
# 配置异常
|
||||
'ConfigError',
|
||||
'ConfigNotFoundError',
|
||||
'ConfigValidationError',
|
||||
# 网络异常
|
||||
'NetworkError',
|
||||
'APIError',
|
||||
# 其他异常
|
||||
'ValidationError',
|
||||
'PermissionError',
|
||||
'ResourceError',
|
||||
# 工具函数
|
||||
'ErrorResult',
|
||||
'handle_error',
|
||||
'wrap_error',
|
||||
'safe_error_message',
|
||||
# 装饰器
|
||||
'catch_errors',
|
||||
]
|
||||
Reference in New Issue
Block a user