2B告警系统验证报告 v2.0.0
日期: 2026-09-12 23:03
范围: run_with_alert.py + alert_state.db
约束: 不改生产cron,不执行交易
一、P1失败后consec变化(场景10)
| 指标 | 修复前 | 修复后 | 预期 |
|---|---|---|---|
| consec变化 | 0→0(未递增) | 1→2 | 每次失败+1 |
| 升级触发 | 未触发 | 触发(tg_sent=1) | consec>=2且静默期外 |
| last_alert_time | 未更新 | 更新为当前时间 | 每次告警更新 |
三条件逻辑验证:
-
静默期内抑制+递增 - 通过
- consec从1增至2
- alert记录(tg_sent=1)
- last_alert_time更新 -
静默期外>=2次升级TG - 通过
- last_alert=10分钟前 > 5分钟阈值
- consec=2 >= P1_ESCALATE_COUNT=2
- should_alert=True -
成功时计数清零 - 通过
- reset_failures()清除consec
- 恢复通知发送(P0/P1)
二、Bug修复记录(6个)
Bug #1: TG测试方法误判
- 现象: 用TG_BOT_TOKEN(错误变量名)测试连通性返回200 OK
- 根因: Telegram API对无效token也返回HTTP 200 + {"ok":false}
- 修复: 必须实际发送消息验证,不能仅检查HTTP状态码
- 教训: API连通性测试需验证业务逻辑,不能仅看HTTP状态
Bug #2: P1计数参数缺失
- 现象: P1任务consecutive_failures始终为0
- 根因: get_consecutive_failures(task_id)硬编码查P0
- 修复: 改为get_consecutive_failures(task_id, level)
- 教训: 多级别系统必须传level参数
Bug #3: had_failure查询时序
- 现象: 恢复通知永不触发(had_failure始终False)
- 根因: record_run_start()先插入running记录,查询到自己的running而非上次failed
- 修复: 将had_failure查询移到record_run_start之前
- 教训: 状态查询必须在状态变更前执行
Bug #4: 失败计数与告警分支耦合
- 现象: TG失败时consec不递增,下次立即升级
- 根因: update_silence()在should_alert分支内,TG失败时不调用
- 修复: 失败计数独立于告警分支,每次失败都递增
- 教训: 状态更新应与业务逻辑解耦
Bug #5: 双重计数
- 现象: consec从1跳到3(预期2)
- 根因: update_silence()的ON CONFLICT DO UPDATE中仍有consecutive_failures + 1
- 修复: 从update_silence()移除consecutive_failures递增
- 教训: 重构时检查所有状态更新路径
Bug #6: 恢复通知不写audit_log
- 现象: 恢复通知成功时只有print,无DB记录
- 根因: 代码遗漏audit_log写入
- 修复: 恢复通知无论TG成功与否都写audit_log
- 教训: 关键路径必须有审计日志
三、TG重试机制
错误分类
| 类型 | 可重试 | 原因 |
|---|---|---|
| ConnectionReset | 是 | 网络瞬时故障 |
| Timeout(连接/读取) | 是 | TG发送是幂等操作 |
| 429 Too Many Requests | 是 | 限流 transient |
| 500/502/503 | 是 | 服务端瞬时故障 |
| 401 Unauthorized | 否 | 认证失败,重试无意义 |
| 400 Bad Request | 否 | 请求错误,重试无意义 |
测试结果
- 401测试: 耗时3.5s(1次请求,不重试)
- ConnectionReset测试: 3次重试后成功
- Timeout测试: 全部重试
四、P1静默期规则
规则表
| 场景 | consec | 静默期 | should_alert | 动作 |
|---|---|---|---|---|
| 第1次失败 | 0->1 | - | False(1<2) | 静默,记日志 |
| 第2次失败(静默期内) | 1->2 | <5min | False | 静默,记日志 |
| 第2次失败(静默期外) | 1->2 | >5min | True | TG告警,更新时间戳 |
| 成功 | - | - | - | 清零consec,发恢复通知 |
关键代码
# 失败计数独立递增(在should_alert判断之后)
if level in ("P0", "P1"):
conn.execute(
"UPDATE silence_state SET consecutive_failures = consecutive_failures + 1 WHERE task_id = ? AND alert_level = ?",
(task_id, level)
)
# 升级条件
failures = get_consecutive_failures(task_id, level) + 1
should_alert = failures >= P1_ESCALATE_COUNT and not check_silence(task_id, level)
五、端到端测试汇总
| 场景 | 条件 | 预期 | 实际 | 状态 |
|---|---|---|---|---|
| 1 | P0成功+TG正常 | task_success | 通过 | 通过 |
| 2 | P0成功+TG_DISABLED | audit_log | 通过 | 通过 |
| 3 | P0失败+TG正常 | tg_sent=1 | 通过 | 通过 |
| 4 | P0失败+TG_DISABLED | audit_log | 通过 | 通过 |
| 5 | P0失败->成功 | recovery通知 | 通过 | 通过 |
| 6 | P0失败->成功+TG_DISABLED | audit_log降级 | 通过 | 通过 |
| 7 | P0连续失败(间隔<30min) | 每次都告警 | 通过 | 通过 |
| 8 | P0失败(last=1小时前) | 告警 | 通过 | 通过 |
| 9 | P1第1次失败 | 静默,consec=1 | 通过 | 通过 |
| 10 | P1第2次失败(静默期外) | 升级告警 | 通过 | 通过 |
| 11 | P1+无效token | 401不重试,降级 | 通过 | 通过 |
六、交付物清单
| 文件 | 状态 | 说明 |
|---|---|---|
| scripts/init_alert_state.py | 完成 | DB schema初始化 |
| scripts/run_with_alert.py | 完成 | 核心包装(485行) |
| data/state/alert_state.db | 完成 | 状态存储(6表) |
| registry/p0_locked_final.md | 完成 | P0优先级清单 |
| docs/git_config_notes.md | 完成 | HTTP/1.1配置说明 |
| reports/2B告警系统验证报告_v2.0.0.md | 完成 | 本报告 |
七、待第3步完成
- [ ] alert_channel_health.py(每15分钟TG健康检查)
- [ ] dead_mans_switch.py(每小时dead-man检查)
- [ ] position_688306_check.py(wrapper脚本)
验证结论: 2B告警系统核心功能完整,11个场景全部通过,可进入影子运行。