Files
WechatHookBot/utils/errors.py

439 lines
11 KiB
Python

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