增加异常处理
This commit is contained in:
parent
dd7eb8d3a1
commit
5a56076858
@ -10,6 +10,7 @@ from contextlib import asynccontextmanager
|
|||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
from app.api import chat, stock, skills, llm, auth, admin, paper_trading, stocks, signals
|
from app.api import chat, stock, skills, llm, auth, admin, paper_trading, stocks, signals
|
||||||
|
from app.utils.error_handler import setup_global_exception_handler, init_error_notifier
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
@ -289,6 +290,23 @@ async def lifespan(app: FastAPI):
|
|||||||
# 启动时执行
|
# 启动时执行
|
||||||
logger.info("应用启动")
|
logger.info("应用启动")
|
||||||
|
|
||||||
|
# 初始化全局异常处理器
|
||||||
|
setup_global_exception_handler()
|
||||||
|
logger.info("全局异常处理器已安装")
|
||||||
|
|
||||||
|
# 初始化飞书错误通知
|
||||||
|
try:
|
||||||
|
from app.services.feishu_service import get_feishu_service
|
||||||
|
feishu_service = get_feishu_service()
|
||||||
|
init_error_notifier(
|
||||||
|
feishu_service=feishu_service,
|
||||||
|
enabled=True, # 启用异常通知
|
||||||
|
cooldown=300 # 5分钟冷却时间
|
||||||
|
)
|
||||||
|
logger.info("✅ 系统异常通知已启用(异常将发送到飞书)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"飞书异常通知初始化失败: {e}")
|
||||||
|
|
||||||
# 启动后台任务
|
# 启动后台任务
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
if getattr(settings, 'paper_trading_enabled', True):
|
if getattr(settings, 'paper_trading_enabled', True):
|
||||||
@ -438,9 +456,19 @@ async def signals_page():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
# 设置全局异常处理器(防止主线程异常退出)
|
||||||
|
setup_global_exception_handler()
|
||||||
|
|
||||||
|
try:
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"app.main:app",
|
"app.main:app",
|
||||||
host=settings.api_host,
|
host=settings.api_host,
|
||||||
port=settings.api_port,
|
port=settings.api_port,
|
||||||
reload=settings.debug
|
reload=settings.debug
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"应用启动失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
raise
|
||||||
|
|||||||
217
backend/app/utils/error_handler.py
Normal file
217
backend/app/utils/error_handler.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
"""
|
||||||
|
全局异常处理器
|
||||||
|
|
||||||
|
捕获系统中所有未处理的异常,并发送飞书通知
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from app.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalExceptionHandler:
|
||||||
|
"""全局异常处理器"""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
_lock = Lock()
|
||||||
|
_initialized = False
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
"""单例模式"""
|
||||||
|
if cls._instance is None:
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化异常处理器"""
|
||||||
|
if GlobalExceptionHandler._initialized:
|
||||||
|
return
|
||||||
|
|
||||||
|
GlobalExceptionHandler._initialized = True
|
||||||
|
self.feishu_service = None
|
||||||
|
self.enabled = True
|
||||||
|
self.last_error_time = None
|
||||||
|
self.error_cooldown = 300 # 错误通知冷却时间(秒),避免重复通知
|
||||||
|
|
||||||
|
logger.info("全局异常处理器初始化完成")
|
||||||
|
|
||||||
|
def set_feishu_service(self, feishu_service):
|
||||||
|
"""设置飞书服务"""
|
||||||
|
self.feishu_service = feishu_service
|
||||||
|
logger.info("异常处理器已连接飞书服务")
|
||||||
|
|
||||||
|
def set_enabled(self, enabled: bool):
|
||||||
|
"""启用或禁用异常通知"""
|
||||||
|
self.enabled = enabled
|
||||||
|
logger.info(f"异常通知已{'启用' if enabled else '禁用'}")
|
||||||
|
|
||||||
|
def set_cooldown(self, seconds: int):
|
||||||
|
"""设置错误通知冷却时间"""
|
||||||
|
self.error_cooldown = seconds
|
||||||
|
logger.info(f"错误通知冷却时间已设置为 {seconds} 秒")
|
||||||
|
|
||||||
|
def handle_exception(self, exc_type, exc_value, exc_traceback):
|
||||||
|
"""
|
||||||
|
处理异常
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exc_type: 异常类型
|
||||||
|
exc_value: 异常值
|
||||||
|
exc_traceback: 异常堆栈
|
||||||
|
"""
|
||||||
|
# 检查是否是键盘中断(用户主动退出)
|
||||||
|
if exc_type == KeyboardInterrupt:
|
||||||
|
logger.info("用户主动中断程序")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查是否启用
|
||||||
|
if not self.enabled:
|
||||||
|
logger.warning("异常通知已禁用,仅记录日志")
|
||||||
|
self._log_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查冷却时间
|
||||||
|
if self.last_error_time:
|
||||||
|
time_since_last = (datetime.now() - self.last_error_time).total_seconds()
|
||||||
|
if time_since_last < self.error_cooldown:
|
||||||
|
logger.warning(f"错误通知冷却中(剩余 {int(self.error_cooldown - time_since_last)} 秒)")
|
||||||
|
self._log_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 记录异常
|
||||||
|
self._log_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
# 发送飞书通知
|
||||||
|
self._send_error_notification(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
# 更新最后错误时间
|
||||||
|
self.last_error_time = datetime.now()
|
||||||
|
|
||||||
|
def _log_exception(self, exc_type, exc_value, exc_traceback):
|
||||||
|
"""记录异常到日志"""
|
||||||
|
logger.error("=" * 60)
|
||||||
|
logger.error("❌ 未捕获的异常")
|
||||||
|
logger.error("=" * 60)
|
||||||
|
logger.error(f"异常类型: {exc_type.__name__}")
|
||||||
|
logger.error(f"异常信息: {str(exc_value)}")
|
||||||
|
logger.error(f"发生时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
|
||||||
|
# 打印完整的堆栈跟踪
|
||||||
|
tb_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
logger.error("堆栈跟踪:\n" + "".join(tb_lines))
|
||||||
|
logger.error("=" * 60)
|
||||||
|
|
||||||
|
def _send_error_notification(self, exc_type, exc_value, exc_traceback):
|
||||||
|
"""发送错误通知到飞书"""
|
||||||
|
if not self.feishu_service:
|
||||||
|
logger.warning("飞书服务未设置,无法发送错误通知")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取异常信息
|
||||||
|
exc_name = exc_type.__name__
|
||||||
|
exc_msg = str(exc_value)
|
||||||
|
exc_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# 获取堆栈跟踪(限制长度)
|
||||||
|
tb_list = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
# 构建堆栈信息(限制长度,避免超出飞书限制)
|
||||||
|
stack_trace = "".join(tb_list)
|
||||||
|
|
||||||
|
# 限制堆栈信息长度(飞书有长度限制)
|
||||||
|
max_length = 3000
|
||||||
|
if len(stack_trace) > max_length:
|
||||||
|
stack_trace = stack_trace[:max_length] + "\n... (堆栈信息过长,已截断)"
|
||||||
|
|
||||||
|
# 格式化堆栈信息,使用代码块
|
||||||
|
formatted_stack = "```\n" + stack_trace + "\n```"
|
||||||
|
|
||||||
|
# 构建飞书消息
|
||||||
|
message = f"""🚨 **系统异常报警**
|
||||||
|
|
||||||
|
**异常类型**: {exc_name}
|
||||||
|
**异常信息**: {exc_msg}
|
||||||
|
**发生时间**: {exc_time}
|
||||||
|
|
||||||
|
**堆栈跟踪**:
|
||||||
|
{formatted_stack}
|
||||||
|
|
||||||
|
⚠️ 请及时处理系统异常"""
|
||||||
|
|
||||||
|
# 发送飞书通知
|
||||||
|
import asyncio
|
||||||
|
try:
|
||||||
|
# 获取当前事件循环
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
# 如果事件循环正在运行,使用 run_coroutine_threadsafe
|
||||||
|
asyncio.run_coroutine_threadsafe(
|
||||||
|
self.feishu_service.send_text(message),
|
||||||
|
loop
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 如果事件循环未运行,直接运行
|
||||||
|
asyncio.run(self.feishu_service.send_text(message))
|
||||||
|
|
||||||
|
logger.info("✅ 已发送异常通知到飞书")
|
||||||
|
|
||||||
|
except RuntimeError:
|
||||||
|
# 没有事件循环,创建新的
|
||||||
|
asyncio.run(self.feishu_service.send_text(message))
|
||||||
|
logger.info("✅ 已发送异常通知到飞书")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发送异常通知失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# 创建全局异常处理器实例
|
||||||
|
_exception_handler: Optional[GlobalExceptionHandler] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_exception_handler() -> GlobalExceptionHandler:
|
||||||
|
"""获取全局异常处理器实例"""
|
||||||
|
global _exception_handler
|
||||||
|
if _exception_handler is None:
|
||||||
|
_exception_handler = GlobalExceptionHandler()
|
||||||
|
return _exception_handler
|
||||||
|
|
||||||
|
|
||||||
|
def setup_global_exception_handler():
|
||||||
|
"""
|
||||||
|
设置全局异常处理器
|
||||||
|
|
||||||
|
捕获所有未处理的异常,并发送飞书通知
|
||||||
|
"""
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
def handle_exception(exc_type, exc_value, exc_traceback):
|
||||||
|
"""异常处理回调函数"""
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
# 设置全局异常钩子
|
||||||
|
sys.excepthook = handle_exception
|
||||||
|
|
||||||
|
logger.info("✅ 全局异常处理器已安装")
|
||||||
|
|
||||||
|
|
||||||
|
def init_error_notifier(feishu_service, enabled: bool = True, cooldown: int = 300):
|
||||||
|
"""
|
||||||
|
初始化错误通知器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
feishu_service: 飞书服务实例
|
||||||
|
enabled: 是否启用异常通知
|
||||||
|
cooldown: 错误通知冷却时间(秒)
|
||||||
|
"""
|
||||||
|
handler = get_exception_handler()
|
||||||
|
handler.set_feishu_service(feishu_service)
|
||||||
|
handler.set_enabled(enabled)
|
||||||
|
handler.set_cooldown(cooldown)
|
||||||
|
|
||||||
|
logger.info("错误通知器初始化完成")
|
||||||
210
docs/ERROR_NOTIFICATION.md
Normal file
210
docs/ERROR_NOTIFICATION.md
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
# 系统异常通知功能使用说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
系统异常通知功能会自动捕获所有未处理的异常,并发送通知到飞书,让你及时了解系统错误。
|
||||||
|
|
||||||
|
## 主要特性
|
||||||
|
|
||||||
|
✅ **自动捕获异常** - 捕获系统中所有未处理的异常
|
||||||
|
✅ **飞书通知** - 自动发送异常详情到飞书
|
||||||
|
✅ **完整堆栈信息** - 包含异常类型、错误信息和完整堆栈跟踪
|
||||||
|
✅ **冷却机制** - 避免重复通知(默认5分钟冷却时间)
|
||||||
|
✅ **可配置** - 可以启用/禁用通知,调整冷却时间
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
### 1. 异常捕获流程
|
||||||
|
|
||||||
|
```
|
||||||
|
系统异常 → 全局异常处理器 → 记录日志 → 检查冷却时间 → 发送飞书通知
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 通知内容
|
||||||
|
|
||||||
|
飞书通知包含以下信息:
|
||||||
|
- 🚨 异常类型(如 `ValueError`, `RuntimeError`)
|
||||||
|
- 📝 异常信息(错误描述)
|
||||||
|
- ⏰ 发生时间
|
||||||
|
- 📚 完整堆栈跟踪(代码格式化显示)
|
||||||
|
|
||||||
|
### 3. 冷却机制
|
||||||
|
|
||||||
|
为了避免同一错误重复通知,系统实现了冷却机制:
|
||||||
|
- 默认冷却时间:300秒(5分钟)
|
||||||
|
- 在冷却期内的异常只记录日志,不发送飞书通知
|
||||||
|
- 冷却时间过后再次出现异常才会发送通知
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 代码配置
|
||||||
|
|
||||||
|
异常通知已在 `main.py` 中自动初始化:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 初始化飞书错误通知
|
||||||
|
from app.services.feishu_service import get_feishu_service
|
||||||
|
feishu_service = get_feishu_service()
|
||||||
|
init_error_notifier(
|
||||||
|
feishu_service=feishu_service,
|
||||||
|
enabled=True, # 启用异常通知
|
||||||
|
cooldown=300 # 5分钟冷却时间
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动态调整配置
|
||||||
|
|
||||||
|
你可以在运行时动态调整异常通知配置:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
# 启用/禁用异常通知
|
||||||
|
handler.set_enabled(True) # 启用
|
||||||
|
handler.set_enabled(False) # 禁用
|
||||||
|
|
||||||
|
# 调整冷却时间(秒)
|
||||||
|
handler.set_cooldown(300) # 5分钟
|
||||||
|
handler.set_cooldown(60) # 1分钟
|
||||||
|
handler.set_cooldown(0) # 禁用冷却(每次都通知)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 示例1: 手动触发异常通知
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
# 手动报告异常
|
||||||
|
try:
|
||||||
|
# 你的代码
|
||||||
|
result = 1 / 0
|
||||||
|
except Exception as e:
|
||||||
|
import sys
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例2: 在函数中装饰器使用
|
||||||
|
|
||||||
|
```python
|
||||||
|
from functools import wraps
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
def notify_on_error(func):
|
||||||
|
"""异常时发送通知的装饰器"""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
handler = get_exception_handler()
|
||||||
|
import sys
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
raise
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@notify_on_error
|
||||||
|
def risky_function():
|
||||||
|
# 可能出错的代码
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
运行测试脚本验证功能:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source backend/venv/bin/activate
|
||||||
|
python scripts/test_error_notification.py
|
||||||
|
```
|
||||||
|
|
||||||
|
测试脚本会:
|
||||||
|
1. 触发各种类型的异常
|
||||||
|
2. 发送飞书通知
|
||||||
|
3. 测试冷却机制
|
||||||
|
4. 验证通知格式
|
||||||
|
|
||||||
|
## 飞书通知示例
|
||||||
|
|
||||||
|
```
|
||||||
|
🚨 **系统异常报警**
|
||||||
|
|
||||||
|
**异常类型**: ZeroDivisionError
|
||||||
|
**异常信息**: division by zero
|
||||||
|
**发生时间**: 2026-02-22 11:50:59
|
||||||
|
|
||||||
|
**堆栈跟踪**:
|
||||||
|
```
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "/app/main.py", line 100, in process_data
|
||||||
|
result = calculate_ratio(x, y)
|
||||||
|
File "/app/utils.py", line 50, in calculate_ratio
|
||||||
|
return x / y
|
||||||
|
ZeroDivisionError: division by zero
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ 请及时处理系统异常
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **敏感信息**: 异常堆栈可能包含敏感信息(如API密钥、密码等),请注意飞书安全性
|
||||||
|
2. **网络依赖**: 发送飞书通知需要网络连接,如果网络异常会只记录日志
|
||||||
|
3. **性能影响**: 异常处理本身对性能影响很小,但频繁异常可能表示系统有问题
|
||||||
|
4. **日志级别**: 异常信息会记录到 ERROR 级别日志,可通过日志系统查询
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 问题1: 没有收到飞书通知
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
- ✅ 飞书 webhook URL 是否正确配置(`.env` 文件中的 `FEISHU_WEBHOOK_URL`)
|
||||||
|
- ✅ `FEISHU_ENABLED=true` 是否设置
|
||||||
|
- ✅ 网络连接是否正常
|
||||||
|
- ✅ 是否在冷却期内
|
||||||
|
- ✅ 查看日志中是否有 "飞书消息发送成功" 或相关错误信息
|
||||||
|
|
||||||
|
### 问题2: 通知太频繁
|
||||||
|
|
||||||
|
解决方案:
|
||||||
|
- 增加冷却时间:`handler.set_cooldown(600)` # 10分钟
|
||||||
|
- 或者临时禁用通知:`handler.set_enabled(False)`
|
||||||
|
|
||||||
|
### 问题3: 想临时禁用通知
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
handler = get_exception_handler()
|
||||||
|
handler.set_enabled(False)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
异常处理器使用 Python 的 `sys.excepthook` 机制,可以捕获所有未处理的异常:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def exception_hook(exc_type, exc_value, exc_traceback):
|
||||||
|
# 处理异常
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
sys.excepthook = exception_hook
|
||||||
|
```
|
||||||
|
|
||||||
|
这种方式可以捕获:
|
||||||
|
- 主线程中的所有未处理异常
|
||||||
|
- 异步任务中的异常(如果未正确捕获)
|
||||||
|
- 脚本运行时的异常
|
||||||
|
|
||||||
|
但不能捕获:
|
||||||
|
- 已被 try-except 捕获的异常(这是设计行为)
|
||||||
|
- 子线程中的异常(需要在子线程中单独处理)
|
||||||
|
- 系统级别的严重错误(如 Segmentation Fault)
|
||||||
218
scripts/test_error_notification.py
Executable file
218
scripts/test_error_notification.py
Executable file
@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
测试系统异常通知功能
|
||||||
|
|
||||||
|
测试场景:
|
||||||
|
1. 测试普通异常通知
|
||||||
|
2. 测试带堆栈信息的异常通知
|
||||||
|
3. 测试冷却时间(避免重复通知)
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 添加项目路径
|
||||||
|
backend_dir = Path(__file__).parent.parent / "backend"
|
||||||
|
sys.path.insert(0, str(backend_dir))
|
||||||
|
|
||||||
|
|
||||||
|
def test_normal_exception():
|
||||||
|
"""测试普通异常通知"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 测试1: 普通异常通知")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
# 模拟一个异常
|
||||||
|
try:
|
||||||
|
result = 1 / 0 # ZeroDivisionError
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 已触发异常处理(请检查飞书是否收到通知)")
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_exception():
|
||||||
|
"""测试自定义异常通知"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 测试2: 自定义异常通知")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
# 模拟一个自定义异常
|
||||||
|
try:
|
||||||
|
raise ValueError("这是一个测试异常,用于验证飞书通知功能")
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 已触发自定义异常处理(请检查飞书是否收到通知)")
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_exception():
|
||||||
|
"""测试嵌套调用异常"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 测试3: 嵌套调用异常(带完整堆栈)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
|
||||||
|
def level_3():
|
||||||
|
"""第三层调用"""
|
||||||
|
raise RuntimeError("深层错误:数据库连接失败")
|
||||||
|
|
||||||
|
def level_2():
|
||||||
|
"""第二层调用"""
|
||||||
|
level_3()
|
||||||
|
|
||||||
|
def level_1():
|
||||||
|
"""第一层调用"""
|
||||||
|
level_2()
|
||||||
|
|
||||||
|
# 模拟嵌套调用异常
|
||||||
|
try:
|
||||||
|
level_1()
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 已触发嵌套异常处理(请检查飞书是否收到完整堆栈信息)")
|
||||||
|
|
||||||
|
|
||||||
|
def test_cooldown():
|
||||||
|
"""测试冷却时间"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 测试4: 冷却时间(连续触发异常)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from app.utils.error_handler import get_exception_handler
|
||||||
|
import time
|
||||||
|
|
||||||
|
handler = get_exception_handler()
|
||||||
|
handler.set_cooldown(10) # 设置10秒冷却时间
|
||||||
|
|
||||||
|
print(f"⏰ 冷却时间设置为 10 秒")
|
||||||
|
|
||||||
|
# 第一次触发
|
||||||
|
try:
|
||||||
|
raise RuntimeError("第一次异常")
|
||||||
|
except Exception:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 第一次异常已处理")
|
||||||
|
|
||||||
|
# 立即第二次触发(应该在冷却期内)
|
||||||
|
print("\n⏳ 立即触发第二次异常(应该在冷却期内)...")
|
||||||
|
try:
|
||||||
|
raise RuntimeError("第二次异常")
|
||||||
|
except Exception:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 第二次异常已处理(应该被冷却,不发送飞书通知)")
|
||||||
|
|
||||||
|
print("\n⏳ 等待 11 秒后再次触发...")
|
||||||
|
time.sleep(11)
|
||||||
|
|
||||||
|
# 第三次触发(冷却期已过)
|
||||||
|
try:
|
||||||
|
raise RuntimeError("第三次异常")
|
||||||
|
except Exception:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 第三次异常已处理(冷却期已过,应该发送飞书通知)")
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_feishu_integration():
|
||||||
|
"""测试与飞书集成的完整流程"""
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("🧪 测试5: 完整集成测试(带飞书服务)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from app.utils.error_handler import setup_global_exception_handler, get_exception_handler
|
||||||
|
from app.services.feishu_service import get_feishu_service
|
||||||
|
|
||||||
|
# 设置全局异常处理器
|
||||||
|
setup_global_exception_handler()
|
||||||
|
print("✅ 全局异常处理器已安装")
|
||||||
|
|
||||||
|
# 初始化飞书通知
|
||||||
|
feishu_service = get_feishu_service()
|
||||||
|
handler = get_exception_handler()
|
||||||
|
handler.set_feishu_service(feishu_service)
|
||||||
|
handler.set_enabled(True)
|
||||||
|
print("✅ 飞书通知已启用")
|
||||||
|
|
||||||
|
# 触发一个测试异常
|
||||||
|
print("\n🔔 触发测试异常...")
|
||||||
|
try:
|
||||||
|
raise Exception("【测试】这是一个集成测试异常,用于验证飞书通知功能是否正常工作")
|
||||||
|
except Exception:
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
handler.handle_exception(exc_type, exc_value, exc_traceback)
|
||||||
|
print("✅ 测试异常已处理,请检查飞书是否收到通知")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主测试函数"""
|
||||||
|
print("\n" + "🚀" * 30)
|
||||||
|
print("系统异常通知功能测试")
|
||||||
|
print("🚀" * 30)
|
||||||
|
|
||||||
|
print("\n⚠️ 注意:以下测试会触发真实的异常,并尝试发送飞书通知")
|
||||||
|
print("⚠️ 请确保飞书 webhook 已正确配置\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 测试1: 普通异常
|
||||||
|
test_normal_exception()
|
||||||
|
|
||||||
|
# 等待用户确认
|
||||||
|
input("\n按 Enter 键继续下一个测试...")
|
||||||
|
|
||||||
|
# 测试2: 自定义异常
|
||||||
|
test_custom_exception()
|
||||||
|
|
||||||
|
# 等待用户确认
|
||||||
|
input("\n按 Enter 键继续下一个测试...")
|
||||||
|
|
||||||
|
# 测试3: 嵌套异常
|
||||||
|
test_nested_exception()
|
||||||
|
|
||||||
|
# 等待用户确认
|
||||||
|
input("\n按 Enter 键继续下一个测试...")
|
||||||
|
|
||||||
|
# 测试4: 冷却时间
|
||||||
|
test_cooldown()
|
||||||
|
|
||||||
|
# 等待用户确认
|
||||||
|
input("\n按 Enter 键继续最后一个测试...")
|
||||||
|
|
||||||
|
# 测试5: 完整集成
|
||||||
|
test_with_feishu_integration()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("✅ 所有测试完成!")
|
||||||
|
print("=" * 60)
|
||||||
|
print("\n📋 请检查飞书聊天窗口,确认是否收到异常通知")
|
||||||
|
print("💡 提示:如果未收到通知,请检查:")
|
||||||
|
print(" 1. 飞书 webhook URL 是否正确")
|
||||||
|
print(" 2. 网络连接是否正常")
|
||||||
|
print(" 3. .env 文件中的 FEISHU_ENABLED 是否为 true\n")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n⚠️ 测试被用户中断")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n\n❌ 测试失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user