""" 统一错误处理模块 提供: - 自定义异常类层次结构 - 错误包装和转换 - 用户友好的错误消息 - 错误日志和追踪 使用示例: 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', ]