案例背景
我们是一个面向 B 端 + C 端混合业务的 SaaS 平台,整体架构是典型的微服务形态:38 个核心服务(账户、租户、计费、订单、网关、消息、推送、文件、搜索、推荐、风控、审计、报表、调度等),跑在自建 K8s 集群上,日均活跃租户 4.2 万、峰值 QPS 3.6 万。日志体系基于 ELK 三件套(Elasticsearch + Logstash + Kibana)+ Filebeat 做采集,再加上 Jaeger 做分布式链路追踪、Prometheus 做指标监控。
这套日志体系从 2024 年 Q1 开始搭建,到 2026 年已经迭代了 2 年半。最初全集群一天才 8 万条日志,随着业务量增长和服务拆分,日志量一路膨胀,到 2026 年 8 月已经稳定在 日均 120 万条,大促或月底账单日出峰值 210 万条。ELK 集群本身扩了 3 轮(从 3 节点到 12 节点,存留期从 30 天压到 14 天),但"看得见日志"和"看得懂日志"之间的鸿沟越来越大。
日志现状的几个硬数据
我们从 ELK 和值班系统拉了 2026 年 7 月整月 + 8 月前三周 的日志数据做基线统计,结果触目惊心:
| 维度 | 数据 | 备注 |
|---|---|---|
| 日均日志条数 | 1,217,400 条 | 工作日均值,周末略低(约 98 万条) |
| 峰值日日志数 | 2,083,000 条 | 8 月 28 日月底账单日 |
| 服务数 | 38 个核心服务 | 分布在 6 个命名空间 |
| 日志级别分布(INFO) | 99.93% | 正常业务流水,绝大部分无价值 |
| 日志级别分布(WARN) | 0.058% | 日均约 700 条,多为可恢复的瞬时抖动 |
| 日志级别分布(ERROR) | 0.011% | 日均约 134 条,会被值班人扫一眼 |
| 日志级别分布(FATAL) | 0.0008% | 日均约 10 条,必处理 |
| 隐式异常日志占比 | 约 0.0007% | 日均约 8-15 条,藏在 INFO 里、语义异常但级别是 INFO |
| SRE 团队人数 | 6 人 | 2 人一组轮值,每周做一次全量审计 |
| 全量日志审计周期 | 1 次/周 | 2 人花 5 个工作日才能完成 |
| 审计覆盖的服务比例 | 100% | 每周必须扫完所有服务 |
| 审计期间发现的真实 Bug | 平均 1.8 个/周 | 99.9% 的日志是正常日志 |
把 120 万条日志按"是否包含异常信号"拆开看,分布是这样的:
- FATAL / 未捕获异常(服务级别直接报错):日均 10 条(0.0008%)——会触发告警,值班人立刻处理
- ERROR(业务报错但可降级):日均 134 条(0.011%)——值班人会扫一眼,但 80% 是已知问题(限流、超时、重试),真正需要排查的不到 20 条
- WARN(边缘抖动):日均 700 条(0.058%)——绝大部分是可恢复的瞬时抖动,几乎没人看
- INFO 中的隐式异常:日均 8-15 条(0.0007%)——这是最危险的。日志级别是 INFO(不会触发告警),但语义上藏着 Bug 信号,比如"重试第 5 次成功""缓存命中率突然从 98% 跌到 41%""某个用户的订单创建耗时 18 秒"。这些日志淹没在 120 万条 INFO 里,人眼永远扫不到
- 正常 INFO:日均约 121 万条(99.93%)——纯业务流水
痛点的三句话总结
- 审计成本高到离谱,但收益极低。2 个 SRE 花 5 个工作日扫 120 万条日志,平均每人每天扫 12 万条。按人眼扫日志 200 条/分钟算,12 万条要扫 10 小时不眠不休——实际根本扫不完,只能靠关键词过滤 + 抽样。审计的"覆盖率"看起来是 100%,实际人工真正读过的日志不到 5%,剩下 95% 是跑脚本过滤 + 看聚合图表。真正藏得深的 Bug,从来不在你过滤的关键词里。
- 99.9% 的正常日志把 0.1% 的异常淹没了。FATAL 和 ERROR 会触发告警,能被发现;但 INFO 级别的隐式异常(重试成功、缓存命中率下跌、慢请求偶发、状态机卡住)永远不会触发告警,也永远不会被人工审计发现。这些 Bug 会潜伏几周甚至几个月,等到某天累积爆掉(比如内存泄漏到 OOM、竞态条件触发数据错乱、错误重试逻辑拖垮下游),才发现"原来日志里早有信号,只是没人看见"。
- 审计频率太低,Bug 潜伏期太长。一周一次的审计节奏,意味着一个 Bug 最长能潜伏 7 天才被发现。8 月做过一次统计:当月发现的 11 个线上 Bug 里,有 6 个在日志里留下了 INFO 级别的早期信号,平均潜伏 4.2 天才被触发成 ERROR/FATAL 被发现。如果审计能做到日级,这 6 个 Bug 都能在爆掉前被掐死。
为什么选 AI 日志异常检测
2026 年 8 月的一次事故彻底推动了这件事。8 月 19 日凌晨 3:42,计费服务的 billing-service Pod 突然 OOM 重启,导致正在跑的月底账单任务中断,4.2 万租户里的 1700 个账单生成失败,需要人工补跑。事后复盘发现:
- 该服务从 8 月 11 日 起日志里就反复出现一条 INFO 级记录:
cache entry evicted: tenant_config size=482MB, max=512MB——缓存驱逐频次从每天 3 次涨到每天 480 次 - 这是一个典型的内存泄漏信号:缓存大小持续增长、驱逐频次持续升高,说明有 key 永远不被淘汰
- 这条日志是 INFO 级别(因为缓存驱逐是正常机制),没有触发任何告警
- 8 月 11 日到 8 月 19 日,整整 8 天,这条信号一直在日志里,但没人看
- 8 月 19 日凌晨,缓存大小突破 512MB 上限,堆外内存也涨爆,OOM
事后算账:如果 8 月 11 日当天就发现这条信号,一个 SRE 花 2 小时排查就能定位内存泄漏,修复后不会有大故障。8 天潜伏期造成的损失是 1700 个账单补跑 + 客户投诉 + SLA 违约金,直接成本约 ¥38 万。
这次事故后,SRE 团队定了 KPI:1 个月内把日志审计从"周级人工"升级为"日级自动化",且要能发现 INFO 级别的隐式异常信号,单次全量审计成本控制在 30 分钟以内。评估了三种方案:
| 方案 | 思路 | 评估结论 |
|---|---|---|
| 加告警规则 | 给所有异常信号加 PromQL/LogQL 告警规则 | 只能匹配"已知模式",对"缓存驱逐频次趋势上涨""重试次数缓慢增加"这种时序模式无能为力;规则数量会爆炸,且新增的隐式异常永远没有对应规则 |
| 传统日志分析平台(Splunk / Datadog Log Patterns) | 接入商业方案,靠 ML 做异常检测 | ML 模型对"语义异常"(比如"重试第 5 次成功"是异常)无能为力,只能做数值异常;且按日志量收费,120 万条/天成本惊人 |
| AI 日志异常检测流水线 | GPT-5 做语义异常识别 + MCP 做日志检索聚合 | 能理解日志语义("重试第 5 次成功"是异常信号)、能发现未知模式、能跨服务关联、能输出可解释的根因假设 |
最终选了第三条路。原因是日志异常检测本质上是语义理解 + 模式发现 + 多源关联的问题。传统告警规则和 ML 模型都只能处理"已知模式的数值异常",而真正藏得深的 Bug 往往是"语义异常"——日志的级别是 INFO,文字也没出现 error/exception,但语义上透露出系统正在出问题。这种判断只有 LLM 做得了。
工具组合与分工
整个日志异常检测流水线由 4 个工具协同完成,每个工具负责一段能力:
| 工具 | 分工 | 为什么是这个工具 |
|---|---|---|
| GPT-5 | 异常模式识别、语义分类、Bug 根因推断、误报过滤 | 200K 上下文能吞下采样后的日志批次(单批约 8 万条精简日志);语义理解能力能识别"重试第 5 次成功""缓存驱逐频次上升"这种 INFO 级隐式异常;能跨日志条目做时序模式分析 |
| 日志分析 MCP | 自动从 ELK 拉日志、按 traceId/service 聚合、做堆栈去重和关键词提取 | GPT-5 直接读 ELK API 要写 Elasticsearch DSL,且单次拉取量大;MCP 封装了"按服务 + 时间窗拉日志""按 traceId 聚合调用链""按堆栈相似度聚类"三个高频操作,并在内部做日志裁剪控制 token |
| Slack MCP | 异常通知路由、@相关人、建 Incident 通道 | 不同严重度的异常走不同 Slack 频道(P0 走 #incident-now 并 @值班 + @服务负责人,低优走 #log-anomaly-low 不 @人);Slack MCP 能根据 GPT-5 的路由决策直接发消息 |
| ELK | 日志存储和检索基础设施 | 已有的日志栈,MCP 直接对接 Elasticsearch API,无需迁移;保留 14 天日志供回溯 |
关键设计原则:GPT-5 出"判断",MCP 出"动作",人做"兜底"。具体来说:
- GPT-5 负责所有需要语义判断的环节:异常模式识别、Bug 根因假设、误报过滤、通知路由。这些是规则做不来的,必须交给 LLM。
- MCP 负责所有需要和数据源交互的动作:拉日志、聚合调用链、发消息。这些是确定性的 IO 操作,封装成 MCP 让 GPT-5 不用写 DSL,直接调用。
- 人只负责低置信度异常的二次确认和Bug 假设的验证修复。GPT-5 对自己的判断输出置信度,低于 0.7 的异常走人工兜底通道,人工处理结果回流成训练数据。
整个流水线的拓扑长这样:
ELK (120万条/天) → 日志分析 MCP (采样降量) → JSON 标准化层
↓
GPT-5 异常模式识别
↓
┌───────────┴───────────┐
↓ ↓
日志分析 MCP (按 traceId 聚合)
(按 service 拉关联)
↓ ↓
└───────────┬───────────┘
↓
GPT-5 根因推断
↓
┌───────────────┴───────────────┐
↓ ↓
Slack MCP 路由通知 (置信度 < 0.7 走人工)
↓ ↓
值班人收到异常 人工兜底通道
↓ ↓
开发者验证修复 反馈回流训练集
流水线架构设计决策
在动手之前,我们讨论了 4 个架构层面的决策,这些决策直接影响了后续实现的稳定性和可维护性,值得单独说明。
决策 1:全量处理 vs 采样降量——选分层采样
120 万条日志一天处理完,是全量喂给 GPT-5 还是采样?选分层采样,原因有三:
- 全量处理不可能:120 万条日志即使每条只占 50 token,也是 6000 万 token,远超 GPT-5 单次 200K 上下文。即使分批,120 万条 × 6K token/批 = 2 万次 API 调用,单月光 API 费用就 ¥18 万,不可承受。
- 全量处理没必要:99.93% 的 INFO 日志是正常业务流水("用户登录成功""订单创建成功"),对异常检测零价值。真正有异常信号的是 ERROR/WARN(约 844 条/天)+ INFO 里的隐式异常(约 8-15 条/天),加起来不到 1000 条。
- 采样能覆盖关键信号:分层采样按"ERROR/FATAL 全量 + WARN 抽样 10% + INFO 抽样 0.1% + 带异常关键词的 INFO 全量"的策略,能把 120 万条压到约 5000 条/天,覆盖 99% 以上的异常信号。
采样策略表:
| 日志级别 | 日均条数 | 采样率 | 采样后条数 | 采样策略 |
|---|---|---|---|---|
| FATAL | 10 | 100% | 10 | 全量保留 |
| ERROR | 134 | 100% | 134 | 全量保留 |
| WARN | 700 | 10% | 70 | 按服务分层抽样,每服务至少 3 条 |
| INFO(含异常关键词) | 约 2000 | 100% | 2000 | 全量保留,关键词表见下方 |
| INFO(其他) | 121 万 | 0.05% | 约 600 | 按服务 + 按小时分层抽样 |
| 合计 | 120 万 | — | 约 2800 | — |
异常关键词表(用于从 INFO 里挑出可能含异常信号的):
anomaly_keywords:
- "retry" # 重试相关
- "fallback" # 降级相关
- "circuit" # 熔断相关
- "evict" # 缓存驱逐
- "timeout" # 超时(即使 INFO 级也可能是异常)
- "slow" # 慢请求
- "degrad" # 降级
- "recover" # 恢复(可能意味着之前异常)
- "reconnect" # 重连
- "queue backlog" # 队列堆积
- "connection pool" # 连接池
- "gc " # GC 相关
- "oom" # OOM 前兆
- "memory" # 内存相关
- "race" # 竞态
- "stale" # 过期/陈旧
- "inconsistent" # 不一致
- "duplicate" # 重复(可能数据错乱)
- "missing" # 缺失
- "unexpected" # 非预期
决策 2:实时流处理 vs 批处理——选日级批处理
日志异常检测是实时流处理(每条日志进来立刻分析)还是批处理(攒一天一起分析)?选日级批处理,原因:
- 实时处理成本爆炸:120 万条/天 = 平均 14 条/秒,峰值 40 条/秒。每条都调 GPT-5 是 ¥18 万/月光 API 费用就拦不住,且 GPT-5 推理延迟 3-8 秒,实时处理根本跟不上日志产生速度。
- 批处理能做时序模式分析:很多隐式异常是时序模式——"缓存驱逐频次从 3 次/天涨到 480 次/天"这种趋势,必须看一天的数据才能发现,单条日志看不出异常。批处理天然支持时序聚合。
- 批处理能做跨日志关联:竞态条件的信号是"两个请求的日志交织出现",必须把一段时间的日志放一起看才能发现。批处理让 GPT-5 能看到全貌。
执行节奏:每天凌晨 2:00(业务低峰)跑一次批处理,处理前一天 0:00-24:00 的全部采样日志,约 30 分钟内完成分析并输出异常报告。值班人早上 8:30 上班时,异常报告已经在 Slack 里等着了。
schedule:
cron: "0 2 * * *" # 每天凌晨 2:00
timezone: "Asia/Shanghai"
processing_window: "前一天 0:00-24:00"
sla: "30 分钟内完成"
output: "Slack 异常报告 + GitHub Issue(P0/P1)"
决策 3:GPT-5 的上下文管理——分批 + 摘要 + 递进
采样后约 2800 条日志,每条平均 80 token(裁剪后),总共约 22 万 token,单次能塞进 GPT-5 的 200K 上下文。但这样会有两个问题:
- 单批太大,GPT-5 容易漏异常:200K 上下文里塞 2800 条日志,GPT-5 的注意力会被高频日志吸引,低频隐式异常容易被忽略。
- 无法做时序聚合:单批处理看不出"驱逐频次趋势"这种时序模式。
所以我们做了三段式递进处理:
- 第 1 段:按服务分批——38 个服务,每个服务的日志单独一批喂给 GPT-5 做异常模式识别。单批约 70 条日志(2800/38),token 约 5600,GPT-5 注意力集中。
- 第 2 段:跨服务关联——第 1 段识别出的异常,按 traceId 跨服务聚合,看是不是同一调用链上的关联异常。
- 第 3 段:全局根因推断——把第 1、2 段的异常汇总,让 GPT-5 做全局根因推断,输出 Bug 假设。
# 三段式处理流程(伪代码)
def run_daily_analysis():
# 第 1 段:按服务分批异常识别
service_anomalies = {}
for service in services: # 38 个服务
logs = log_mcp.get_sampled_logs(service, window="yesterday")
anomalies = gpt5.detect_anomalies(logs, service=service)
service_anomalies[service] = anomalies
# 第 2 段:跨服务关联聚合
all_anomalies = flatten(service_anomalies.values())
correlated = log_mcp.aggregate_by_trace(all_anomalies)
correlated_clusters = gpt5.cluster_related_anomalies(correlated)
# 第 3 段:全局根因推断
bug_hypotheses = gpt5.infer_root_causes(
anomalies=all_anomalies,
correlated_clusters=correlated_clusters,
service_anomalies=service_anomalies
)
# 路由通知
for hypothesis in bug_hypotheses:
route_and_notify(hypothesis)
决策 4:GPT-5 的调用是同步 vs 异步——选异步
日志分析是同步调 GPT-5 等结果,还是先入队再异步消费?选异步,原因有三:
- GPT-5 单次推理 3-8 秒,38 个服务如果串行处理要 4 分钟,并行 8 worker 也要 1 分钟。同步处理会让定时任务总耗时不可控。
- 失败重试更干净:异步队列(Kafka)天然支持重试、死信队列、回放。某个服务这批日志处理失败,不影响其他服务。
- 便于降级:当 GPT-5 API 限流或故障时,异步队列可以缓存待处理批次,等 API 恢复后继续,不会丢数据。
所以采样后的日志写入 Kafka topic logs.sampled,下游有 3 个 consumer group 分别消费:异常识别 consumer、关联分析 consumer、根因推断 consumer。每个 consumer 独立消费、独立失败、独立重试,互不影响。consumer 配置:
consumer_group: ai-log-detector
concurrency: 8 # 8 个并发 worker
max_poll_records: 16
session_timeout: 120s # GPT-5 推理可能慢
max_retry: 3
retry_backoff: 30s
dead_letter_topic: logs.detector.dlq
死信队列的处理:连续 3 次处理失败的批次进 DLQ,DLQ 的 consumer 是降级逻辑——不调 GPT-5,直接按简单规则(关键词匹配)做异常识别,结果降级路由到 #log-anomaly-degraded 频道提示"AI 分析降级,请人工复核"。保证即使 GPT-5 挂了,日志审计也不会完全失能。
决策 5:反馈数据存哪——选 PostgreSQL
人工反馈数据(开发者对 GPT-5 Bug 假设的确认/否定)存哪?选 PostgreSQL,原因:
- 需要按维度查询:"本周所有 false positive 的异常""billing-service 的所有历史 Bug"这种查询,文件做不到。
- 去重和冲突处理:同一条异常可能被两个开发者反馈,需要
ON CONFLICT处理。 - 和训练样本生成联动:周末批处理跑 few-shot 样本抽取,直接查 DB。
表结构:
CREATE TABLE log_anomaly_feedback (
id BIGSERIAL PRIMARY KEY,
anomaly_id VARCHAR(128) NOT NULL,
anomaly_json JSONB NOT NULL,
ai_pattern_type VARCHAR(32), -- GPT-5 识别的异常类型
ai_bug_hypothesis JSONB, -- GPT-5 的 Bug 假设
ai_confidence FLOAT, -- GPT-5 的置信度
human_confirmed BOOLEAN, -- 人工是否确认为真 Bug
human_false_positive BOOLEAN, -- 人工是否标为误报
human_bug_category VARCHAR(32), -- 人工标注的 Bug 类型
human_reason TEXT,
fixed_commit VARCHAR(40), -- 修复的 commit
confirmed_at TIMESTAMPTZ,
confirmed_by VARCHAR(64),
is_used_for_training BOOLEAN DEFAULT FALSE,
training_batch_id VARCHAR(64),
UNIQUE (anomaly_id, confirmed_by)
);
CREATE INDEX idx_feedback_not_used ON log_anomaly_feedback (is_used_for_training)
WHERE is_used_for_training = FALSE;
CREATE INDEX idx_feedback_service ON log_anomaly_feedback ((anomaly_json->>'service'));
实施步骤
整个流水线分 5 步,第 0 步是数据预处理,第 1-3 步是 AI 分析主流程,第 4 步是人工验证和反馈闭环。下面每一步都给出可复制的 Prompt 模板和操作说明。
第 0 步:日志采集和预处理(ELK query → JSON 标准化 → 采样降量)
这一步的目标是把 ELK 里原始的日志标准化成 GPT-5 能稳定理解的 JSON 结构,并通过采样把 120 万条压到约 2800 条。
ELK 里原始的日志(通过 Filebeat 采集)长这样:
{
"_index": "filebeat-2026.09.08",
"_source": {
"@timestamp": "2026-09-08T14:23:01.234Z",
"log": {
"level": "INFO",
"logger": "com.company.billing.cache.ConfigCache",
"file": {"path": "/var/log/billing/billing.log"},
"offset": 48237123
},
"service": {"name": "billing-service", "version": "2.14.3"},
"kubernetes": {
"pod": "billing-service-7d4f-x9k2",
"namespace": "prod-billing",
"node": "node-pool-a-12"
},
"message": "cache entry evicted: tenant_config size=482MB, max=512MB",
"trace_id": "a1b2c3d4e5f6",
"span_id": "1a2b3c4d",
"tenant_id": "T_48217",
"host": "10.0.12.45"
}
}
这个结构有几个问题:字段嵌套深(log.level / kubernetes.pod)、字段语义不统一(有的服务日志用 levelname 有的用 log.level)、message 是纯文本(GPT-5 要解析"cache entry evicted"这种自然语言)。直接喂给 GPT-5 会导致它把精力花在解析结构上。
所以我们写了一个标准化中间层(一个 250 行的 Python 服务),把 ELK 的日志转成扁平化的标准化 JSON:
{
"log_id": "log-2026-09-08-142301-billing-x9k2-48217123",
"timestamp": "2026-09-08T14:23:01.234Z",
"level": "INFO",
"service": "billing-service",
"service_version": "2.14.3",
"pod": "billing-service-7d4f-x9k2",
"namespace": "prod-billing",
"node": "node-pool-a-12",
"logger": "com.company.billing.cache.ConfigCache",
"message": "cache entry evicted: tenant_config size=482MB, max=512MB",
"trace_id": "a1b2c3d4e5f6",
"span_id": "1a2b3c4d",
"tenant_id": "T_48217",
"host": "10.0.12.45",
"is_sampled": true,
"sample_reason": "anomaly_keyword:evict"
}
标准化层做的几件事:
- 扁平化:把
log.level提到顶层level,把kubernetes.pod提到顶层pod - 统一 level:把
INFO/info/Information/I等不同命名统一成DEBUG/INFO/WARN/ERROR/FATAL五档 - 生成 log_id:用
时间 + service + pod + offset生成稳定 ID - 采样打标:根据采样策略给每条日志标
is_sampled和sample_reason,方便后续追溯 - 异常关键词预筛:对 INFO 日志做关键词匹配,命中的标
has_anomaly_keyword: true并全量保留
标准化层还做了日志去重和堆栈折叠:完全相同的日志(同 service + 同 message + 1 秒内)只保留一条并标 repeat_count;含堆栈的 ERROR 日志,堆栈折叠成"第一行 + 末尾 3 行 + 总行数",避免一条日志占 8000 token。
采样后的日志写入 Kafka topic logs.sampled,下游 GPT-5 消费者消费。Kafka 配置:
topic: logs.sampled
partitions: 12
replication: 3
retention: 7d
cleanup.policy: delete
这一步不用 Prompt,它是确定性的转换服务。但它产出的 JSON 是后续所有 Prompt 的输入基础,格式稳定后整个流水线的准确率才能稳。
第 1 步:异常模式识别(GPT-5 语义分析 Prompt)
采样日志进 Kafka 后,第一件事是让 GPT-5 对每个服务的日志批次做异常模式识别。目标不是找 ERROR(ERROR 已经有告警了),而是找 INFO/WARN 级别里藏着的隐式异常信号。
这一步是整个流水线的核心,也是最难的。关键挑战是:怎么让 GPT-5 理解"什么样的日志算异常"。我们试过让 GPT-5 "找出异常日志",结果它把所有 WARN 都标成异常,把所有"含 timeout 关键词的 INFO"都标成异常——这些大部分是已知且可恢复的,不是真异常。
最终稳定的 Prompt 设计思路是:给 GPT-5 一套明确的"异常模式"分类标准,让它按标准判断,而不是凭感觉。异常模式分 6 类:
| 异常模式 | 含义 | 示例日志 |
|---|---|---|
| trend_shift | 指标趋势性变化 | "cache eviction count: today=480, avg=3" |
| retry_anomaly | 重试行为异常 | "retry attempt 5 succeeded"(正常不该重试 5 次) |
| latency_outlier | 延迟离群 | "request completed in 18342ms"(正常 200ms) |
| state_inconsistency | 状态不一致 | "order status: PAID but inventory not deducted" |
| resource_pressure | 资源压力信号 | "connection pool: active=48/50" |
| unusual_pattern | 罕见模式 | 该服务从未出现过的日志模式 |
这一步的 Prompt 模板(精简版,完整版见后文"关键 Prompt 模板"小节):
你是 SRE 团队的日志异常检测助手。给你一个服务一天的采样日志(约 70 条),
请识别其中隐藏的异常信号。
重点:不要只看 ERROR/FATAL(那些已经被告警系统覆盖了)。
你要找的是 INFO/WARN 级别里藏着的、不会触发告警但语义上异常的信号。
异常模式分 6 类:
1. trend_shift:某个指标在日志里呈现趋势性变化
(比如缓存驱逐频次从 3 次/天涨到 480 次/天)
2. retry_anomaly:重试行为异常
(比如重试第 5 次才成功,正常不该重试这么多次)
3. latency_outlier:延迟离群
(某次请求耗时远超正常水平,即使最终成功)
4. state_inconsistency:状态不一致
(日志显示业务状态机卡住或矛盾)
5. resource_pressure:资源压力信号
(连接池/线程池/缓存接近上限)
6. unusual_pattern:该服务从未出现过的罕见日志模式
对每条疑似异常日志,输出:
- pattern_type:上述 6 类之一
- log_id:日志 ID
- anomaly_signal:一句话说明为什么异常
- severity:high / medium / low
- confidence:0.0-1.0
日志批次:
{{logs_json}}
这个 Prompt 之所以稳定,关键在三点:
- 明确告诉 GPT-5 "不要只看 ERROR":否则它会偷懒只标 ERROR,那和告警系统没区别。
- 给 6 类异常模式的明确分类:让 GPT-5 按标准判断,而不是凭感觉。这把异常识别准确率从 62% 拉到 91%。
- 每类给具体示例:示例锚定了 GPT-5 的判断尺度,避免它把"重试 1 次"也标成异常。
实际产出的一个异常识别示例(输入是 billing-service 的采样日志):
{
"anomalies": [
{
"pattern_type": "trend_shift",
"log_id": "log-2026-09-08-142301-billing-x9k2-48217123",
"anomaly_signal": "缓存驱逐频次从历史均值 3 次/天升至今天的 480 次/天,且驱逐的 cache size 持续增长(从 280MB 涨到 482MB),疑似内存泄漏或缓存策略失效",
"severity": "high",
"confidence": 0.87
},
{
"pattern_type": "retry_anomaly",
"log_id": "log-2026-09-08-091522-billing-x9k2-48217110",
"anomaly_signal": "billing 调用 payment-service 的请求重试第 5 次才成功,正常重试上限是 3 次,疑似下游服务间歇性不可用",
"severity": "medium",
"confidence": 0.74
}
],
"no_anomaly_logs_count": 68,
"scan_summary": "扫描 70 条日志,识别 2 条疑似异常,其余 68 条正常"
}
这个异常识别结果会带着原日志一起传给第 2 步做跨服务关联。
第 2 步:日志聚合和关联分析(日志分析 MCP 按 traceId/service 聚合)
第 1 步识别出的异常是单服务视角的,但很多 Bug 的信号分散在多个服务的日志里——比如竞态条件的信号是"两个服务的日志交织出现",内存泄漏的信号是"缓存服务驱逐 + 调用方服务重试"。这一步的目标是用日志分析 MCP 把分散的异常按 traceId 和调用链聚合起来,还原跨服务的关联。
为什么不让 GPT-5 直接调 ELK API 做聚合:我们试过让 GPT-5 直接写 Elasticsearch DSL 做 traceId 聚合,结果它写的 query 经常语法错误(trace_id 字段在不同索引里命名不一致、时间范围用相对时间而非绝对 ISO 时间),而且单次返回数据量大、token 消耗惊人(一条调用链的完整日志可能 1.2 万 token,一次拉 20 条链路就 24 万 token,直接打爆上下文)。封装成 MCP 后,MCP 在内部做了调用链裁剪(只保留每个 span 的关键日志)、traceId 补全(跨服务 traceId 断链时做模糊匹配),GPT-5 只需要调 aggregate_by_trace(trace_ids) 就行。
日志分析 MCP 提供三个工具:
| 工具 | 入参 | 出参 | 用途 |
|---|---|---|---|
aggregate_by_trace | trace_id 列表 | 按调用链聚合的日志(每 span 关键日志) | 还原跨服务调用链,找关联异常 |
get_related_service_logs | service, time_window | 上下游服务日志 | 拉上下游服务同时间窗日志,找关联 |
cluster_similar_anomalies | anomaly 列表 | 按语义相似度聚类的异常组 | 把 30 条异常聚成 3-5 组,避免 GPT-5 处理重复 |
这一步的 Prompt 模板(精简版):
你已完成单服务异常识别,现在需要做跨服务关联分析。
已有信息:
【各服务异常列表】
{{service_anomalies}}
请按以下顺序操作:
1. 从异常列表里提取所有 trace_id,调用日志分析 MCP 的 aggregate_by_trace,
参数:trace_ids = {{提取的 trace_id 列表}}
2. 对没有 trace_id 但疑似跨服务的异常,调用 get_related_service_logs,
参数:service = {{异常所在服务}}, time_window = {{异常时间前后 5 分钟}}
3. 调用 cluster_similar_anomalies,把所有异常按语义相似度聚类,
找出可能是同一根因的异常组
4. 输出关联分析结果:
- 每个异常组:包含哪些异常、它们如何关联、共同根因假设
- 孤立异常:无法关联到其他的异常,单独标注
实际产出的一个关联分析示例(输入是 billing-service 的缓存驱逐异常 + payment-service 的重试异常):
{
"correlated_clusters": [
{
"cluster_id": "cluster-1",
"anomalies": [
"billing-service: cache eviction trend_shift (480次/天)",
"payment-service: retry_anomaly (重试第5次成功)",
"gateway: latency_outlier (billing 请求 18秒)"
],
"correlation": "billing-service 缓存驱逐频次飙升 → 缓存命中率下跌 → payment-service 调用 billing 时缓存未命中导致慢 → gateway 观察到 billing 请求延迟离群。三者通过 trace_id a1b2c3d4e5f6 关联,是同一调用链上的连锁异常",
"common_root_hypothesis": "billing-service 缓存策略异常导致连锁慢请求",
"confidence": 0.81
}
],
"isolated_anomalies": [
"user-service: unusual_pattern (新出现的鉴权日志模式,无关联)"
]
}
这个关联结果会传给第 3 步做 Bug 根因推断。
第 3 步:Bug 根因推断(GPT-5 综合分析 → 输出 Bug 假设、证据链、置信度)
第 2 步的关联分析把异常聚成了"可能属于同一根因"的组,但还没说根因到底是什么。这一步让 GPT-5 做全局根因推断,输出具体的 Bug 假设——不是"billing-service 缓存异常"这种笼统的话,而是"billing-service 的 ConfigCache 有内存泄漏,某个 key 永远不被淘汰,导致缓存驱逐频次持续上涨,最终 OOM"这种可执行的根因。
这一步的 Prompt 模板(精简版):
你是 SRE 团队的 Bug 根因推断助手。基于异常识别 + 跨服务关联分析,
输出具体的 Bug 假设。
已有信息:
【各服务异常列表】
{{service_anomalies}}
【跨服务关联分析】
{{correlated_clusters}}
请输出 1-3 个 Bug 假设,按可能性排序。每个假设包含:
- bug_hypothesis:一句话描述 Bug(要具体到代码层,比如"X 服务的 Y 模块有 Z 问题")
- evidence_chain:证据链,每条引用具体日志行
- affected_services:受影响的服务
- expected_symptom:如果这个 Bug 成立,应该还能观察到什么现象
- suggested_investigation:建议开发者怎么验证(查哪段代码、看哪个指标)
- confidence:0.0-1.0
如果证据不足,输出 insufficient_evidence 并说明缺什么数据。
实际产出的一个根因推断示例:
{
"bug_hypotheses": [
{
"rank": 1,
"bug_hypothesis": "billing-service 的 ConfigCache 存在内存泄漏:某个 tenant_config 的 key 永远不被 LRU 淘汰,导致缓存大小持续增长、驱逐频次飙升,最终 OOM",
"evidence_chain": [
"billing-service 日志:cache entry evicted: tenant_config size=482MB, max=512MB(8月11日起出现,频次从3次/天涨到480次/天)",
"billing-service 日志:缓存驱逐的总是非 tenant_config 的 key(驱逐日志显示 evicted key=feature_flag_xxx,而非 tenant_config)",
"billing-service 日志:tenant_config 的 size 持续增长(280MB→482MB,8天内)",
"关联:gateway 观察到 billing 请求延迟离群(18秒),疑似缓存满导致查询阻塞"
],
"affected_services": ["billing-service", "gateway"],
"expected_symptom": "如果假设成立,billing-service 的堆内存使用应持续上涨、GC 频次升高、tenant_config 缓存的 key 数量持续增长",
"suggested_investigation": [
"1. 查 ConfigCache 的 LRU 实现代码,看 tenant_config 的 key 是否被特殊保留(比如被外部引用导致 LRU 无法淘汰)",
"2. 看 billing-service 的 JVM 堆内存监控,确认是否持续上涨",
"3. dump 堆内存,看 tenant_config 缓存的 key 数量"
],
"confidence": 0.84
}
],
"data_gaps": [
"未拿到 ConfigCache 的源码,无法确认 LRU 实现是否有 bug"
]
}
这个根因推断结果会作为 Slack 通知的正文和 GitHub Issue 的内容(第 4 步)。
第 4 步:人工验证和修复(开发者确认 → 创建 Issue → 修复 → 回归验证)
根因推断完成后,需要让开发者验证 GPT-5 的假设是不是对的。这一步的核心是:GPT-5 的输出是"假设"不是"结论",必须人工验证后才能动手改代码。
路由规则表(根据严重度和置信度差异化路由):
| 严重度 + 置信度 | GitHub Issue | Slack 频道 | @ 人 | 人工兜底 |
|---|---|---|---|---|
| high severity + confidence > 0.8 | 自动建 Issue(label: bug/P0) | #log-anomaly-high | @值班 + @服务负责人 | 否 |
| high severity + confidence < 0.8 | 建 Issue 标 needs-review | #log-anomaly-review | @值班 | 是 |
| medium severity | 建 Issue(label: bug/P1) | #log-anomaly-medium | @服务负责人 | 否 |
| low severity | 不建 Issue,只记日志 | #log-anomaly-low | 不 @人 | 否 |
| 任何 + confidence < 0.6 | 建 Issue 标 needs-review | #log-anomaly-review | @值班 | 是 |
这一步的 Prompt 模板(精简版,负责路由决策):
你已完成 Bug 根因推断,现在需要做通知路由和 Issue 创建。
已有信息:
【Bug 假设】{{bug_hypotheses}}
请按以下规则操作:
1. 判断严重度(high/medium/low)和置信度
2. 根据路由规则表决定 Slack 频道和 @人
3. 调用 GitHub MCP 的 create_issue
4. 调用 Slack MCP 的 send_message
Issue body 模板:
## AI 识别的异常
{{anomalies 渲染}}
## Bug 假设
{{hypotheses 渲染}}
## 建议验证步骤
{{suggested_investigation}}
## 相关日志
{{log_ids}}
开发者验证流程:
- 服务负责人收到 Slack 通知,看到 Bug 假设 + 证据链 + 建议验证步骤
- 开发者按
suggested_investigation查代码、看指标,确认或否定假设 - 如果确认:开发者在 Issue 里回复"假设确认,根因是 XXX",开始修复
- 如果否定:开发者回复"假设不成立,实际是 YYY",这个否定会回流到反馈表,用于训练 GPT-5
- 修复后:开发者在 Issue 里贴 commit hash,标记 resolved
- 回归验证:修复上线后,下一次日批处理会重新扫该服务的日志,确认异常信号消失
反馈闭环的实际效果:
- 第 1 周:异常识别准确率 62%,误报率 18%
- 第 2 周(加入 180 条反馈样本后):准确率 78%,误报率 11%
- 第 4 周(累计 720 条反馈样本):准确率 86%,误报率 6%
- 第 8 周(累计 1640 条反馈样本):准确率 91%,误报率 4%
这是整个流水线最关键的设计:没有反馈闭环,GPT-5 的判断准确率会停滞在 62% 左右(zero-shot 水平),永远到不了 91%。反馈闭环让流水线成了能持续学习的系统。
端到端完整案例走查
为了把整个流水线串起来,这里走查一次真实的日批处理过程。这是 2026 年 9 月 8 日凌晨 2:00 触发的一次批处理,处理 9 月 7 日 0:00-24:00 的全部采样日志,最终在 2:27 输出异常报告,耗时 27 分钟,发现了 3 个隐藏 Bug。
起点:采样层拉取日志(2:00-2:03,耗时 3 分钟)
凌晨 2:00,定时任务触发。标准化层从 ELK 拉取 9 月 7 日的全量日志,做采样:
拉取时间窗:2026-09-07T00:00:00Z ~ 2026-09-07T23:59:59Z
总日志条数:1,184,327 条
采样后条数:2,743 条
采样率:0.23%
按服务分布:
billing-service: 78 条(含 12 条 anomaly_keyword 命中)
payment-service: 65 条(含 8 条 anomaly_keyword)
order-service: 82 条(含 15 条 anomaly_keyword)
...(共 38 个服务)
采样后的日志写入 Kafka topic logs.sampled。
第 1 步:按服务分批异常识别(2:03-2:14,耗时 11 分钟)
38 个服务的日志分 38 批喂给 GPT-5。每批约 70 条日志,token 约 5600,GPT-5 单次推理 3-5 秒。38 批并行 8 个 worker,总耗时 11 分钟。
以 billing-service 这一批为例,GPT-5 的推理过程(从日志还原):
扫描 78 条日志:
- 12 条含 anomaly_keyword(evict × 8, retry × 3, slow × 1)
- 66 条正常 INFO
分析 evict 日志:
- 8 条都是 "cache entry evicted: tenant_config size=XXX, max=512MB"
- size 值序列:280MB, 312MB, 358MB, 401MB, 442MB, 467MB, 481MB, 482MB
- 趋势:8 天内从 280MB 涨到 482MB,接近 512MB 上限
- 历史基线:该服务缓存驱逐频次正常 3 次/天,今天 480 次/天
- 判定:trend_shift,severity=high,confidence=0.87
分析 retry 日志:
- 3 条 "retry attempt N succeeded",N=3,4,5
- 重试第 5 次才成功,正常重试上限 3 次
- 判定:retry_anomaly,severity=medium,confidence=0.74
输出 2 条异常,76 条正常
38 个服务扫描完,共识别出 11 条疑似异常(分布在 7 个服务)。
第 2 步:跨服务关联分析(2:14-2:19,耗时 5 分钟)
GPT-5 调用日志分析 MCP 把 11 条异常按 traceId 聚合:
调用 1:aggregate_by_trace(耗时 1.8 秒)
参数:trace_ids = [a1b2c3d4e5f6, b2c3d4e5f6a1, ...](从 11 条异常里提取)
MCP 返回 3 条调用链的聚合日志,GPT-5 看到其中一条链路 a1b2c3d4e5f6 串联了 billing + payment + gateway 三个服务的异常。
调用 2:cluster_similar_anomalies(耗时 1.2 秒)
参数:anomalies = 11 条异常
MCP 返回聚类结果:11 条异常聚成 4 组,其中 1 组含 3 条异常(billing 缓存驱逐 + payment 重试 + gateway 延迟离群),其余 3 组各 1-2 条。
GPT-5 综合输出:
{
"correlated_clusters": [
{
"cluster_id": "c1",
"anomalies": ["billing: cache eviction trend", "payment: retry anomaly", "gateway: latency outlier"],
"correlation": "同一 trace_id a1b2c3d4e5f6 串联,billing 缓存慢 → payment 重试 → gateway 延迟",
"confidence": 0.81
},
{
"cluster_id": "c2",
"anomalies": ["order: state_inconsistency"],
"correlation": "孤立异常,无关联",
"confidence": 0.42
}
]
}
第 3 步:Bug 根因推断(2:19-2:24,耗时 5 分钟)
GPT-5 基于关联分析输出 3 个 Bug 假设:
{
"bug_hypotheses": [
{
"rank": 1,
"bug_hypothesis": "billing-service ConfigCache 内存泄漏:tenant_config 的 key 永远不被 LRU 淘汰",
"confidence": 0.84,
"severity": "high"
},
{
"rank": 2,
"bug_hypothesis": "order-service 状态机竞态条件:并发更新订单状态时未加锁,导致 PAID 但未扣库存",
"confidence": 0.71,
"severity": "high"
},
{
"rank": 3,
"bug_hypothesis": "payment-service 重试逻辑错误:对 5xx 重试但对 4xx 也重试,导致下游压力倍增",
"confidence": 0.68,
"severity": "medium"
}
]
}
第 4 步:通知路由(2:24-2:27,耗时 3 分钟)
GPT-5 按路由规则发 Slack + 建 Issue:
- 假设 1(high, conf=0.84):建 Issue #2847,发
#log-anomaly-high,@值班 + @billing-owner - 假设 2(high, conf=0.71):建 Issue #2848 标
needs-review,发#log-anomaly-review,@值班 - 假设 3(medium, conf=0.68):建 Issue #2849 标
needs-review,发#log-anomaly-review,@值班
值班人早上 8:30 上班,看到 3 条异常报告。假设 1 高置信度直接验证,假设 2、3 走人工兜底。
三个假设后续验证结果:假设 1 确认(内存泄漏),假设 2 确认(竞态条件),假设 3 部分确认(重试逻辑有问题但描述不完全准确)。准确率 2.5/3 = 83%(这次批处理的准确率,8 周平均是 91%)。
关键 Prompt 模板
这一节给出三个核心 Prompt 的完整可复制版本。这三个 Prompt 直接决定了异常检测的准确率。
Prompt 模板 1:异常模式识别
你是 SRE 团队的日志异常检测助手。你的任务是扫描一个服务一天的采样日志,
识别其中隐藏的异常信号。
## 重要原则
1. 不要只看 ERROR/FATAL——那些已经被告警系统覆盖了。你的核心价值是发现
INFO/WARN 级别里藏着的、不会触发告警但语义上异常的信号。
2. 不要把所有 WARN 都标异常——大部分 WARN 是可恢复的瞬时抖动,只有
表现出趋势性、频次异常、或语义矛盾的才是真异常。
3. 不要凭感觉——必须按下方 6 类异常模式的标准判断,每条异常要说明
命中了哪类模式、为什么。
## 异常模式分类标准
### 1. trend_shift(趋势性变化)
某个指标在日志里呈现趋势性变化(相比历史基线)。
判定信号:
- 某个数值(计数、大小、频次)在一天内持续上涨或下跌
- 相比历史均值变化超过 3 倍
- 即使最终值仍在"正常范围",但趋势本身异常
示例:
- "cache eviction count: today=480, avg=3"(驱逐频次涨 160 倍)
- "connection pool active: 8:00=12, 12:00=38, 18:00=45(max=50)"(持续上涨接近上限)
### 2. retry_anomaly(重试行为异常)
重试行为不符合预期。
判定信号:
- 重试次数超过正常上限(默认 3 次,服务可自定义)
- 重试成功但重试次数异常高(说明下游间歇性不可用)
- 不该重试的错误码被重试(比如对 4xx 重试)
示例:
- "retry attempt 5 succeeded"(正常上限 3 次)
- "retrying 404 Not Found, attempt 2"(4xx 不该重试)
### 3. latency_outlier(延迟离群)
某次请求耗时远超正常水平,即使最终成功。
判定信号:
- 单次请求耗时 > 服务 P99 的 3 倍
- 批量请求中有个别耗时离群
示例:
- "request completed in 18342ms"(正常 P99=200ms)
- "batch processed: 9999 succeeded in 50ms, 1 succeeded in 8s"
### 4. state_inconsistency(状态不一致)
日志显示业务状态机卡住或矛盾。
判定信号:
- 状态转换不符合预期(比如 PAID 后未扣库存)
- 同一实体的状态在不同服务间不一致
- 状态机卡在某个中间态超时
示例:
- "order status: PAID but inventory not deducted"
- "task stuck in PENDING for 4 hours"
### 5. resource_pressure(资源压力信号)
资源接近上限,即使还没报错。
判定信号:
- 连接池 active > 80% of max
- 线程池 queue size > 50% of max
- 缓存 hit rate 跌破历史 P5
- 队列 backlog 持续上涨
示例:
- "connection pool: active=48/50"
- "cache hit rate: 41% (baseline 98%)"
### 6. unusual_pattern(罕见模式)
该服务从未出现过的日志模式(需要和该服务历史日志对比)。
判定信号:
- 日志 message 是该服务历史上从未出现过的内容
- 日志的触发频次远超历史(比如某日志历史上 1 次/周,今天 20 次)
- 日志的出现时间异常(比如只在凌晨出现的日志白天出现了)
示例:
- 该服务首次出现 "deadlock detected" 日志
- 某鉴权日志历史上 0 次,今天出现 12 次
## 输入
你会收到一个 JSON 数组,每个元素是一条采样日志,字段:
log_id, timestamp, level, service, pod, logger, message, trace_id, sample_reason
## 输出格式
严格输出以下 JSON,不要输出任何其他内容:
{
"anomalies": [
{
"pattern_type": "trend_shift|retry_anomaly|latency_outlier|state_inconsistency|resource_pressure|unusual_pattern",
"log_id": "...",
"anomaly_signal": "一句话说明为什么异常(必须引用具体数值或日志内容)",
"severity": "high|medium|low",
"confidence": 0.0,
"related_metrics": ["如果能看到趋势,列出关键数值序列"]
}
],
"scan_summary": {
"total_logs": N,
"anomaly_count": M,
"no_anomaly_logs": N-M
}
}
severity 判定标准:
- high:疑似会导致服务故障(内存泄漏、数据错乱、死锁)
- medium:影响性能或可靠性,但未到故障(重试异常、延迟离群)
- low:值得关注但暂无影响(资源水位偏高、罕见模式)
## 日志批次
{{logs_json}}
Prompt 模板 2:日志聚合和关联分析
你是 SRE 团队的日志关联分析助手。你的任务是把各服务识别出的异常按
traceId 和调用链聚合,找出属于同一根因的异常组。
## 输入
1. 各服务异常列表(JSON 数组,每条含 pattern_type/log_id/service/message/trace_id/timestamp)
2. 你需要主动调用日志分析 MCP 获取关联数据
## 操作步骤
### 步骤 1:按 trace_id 聚合调用链
从异常列表里提取所有 trace_id(去重),调用日志分析 MCP 的 aggregate_by_trace:
- 参数:trace_ids = {{提取的 trace_id 列表}}
- MCP 返回每条调用链的跨服务日志聚合(每 span 关键日志)
拿到调用链后,分析:
- 哪些异常在同一条调用链上(说明是连锁异常)
- 调用链上的异常传播方向(上游 → 下游)
### 步骤 2:对无 trace_id 的异常做时间窗关联
有些异常没有 trace_id(比如缓存驱逐日志通常不带 trace_id),按时间窗关联:
调用日志分析 MCP 的 get_related_service_logs:
- 参数:service = {{异常所在服务}}, time_window = {{异常时间前后 5 分钟}}
- 看同时间窗内其他服务有没有相关异常
### 步骤 3:异常聚类
调用日志分析 MCP 的 cluster_similar_anomalies:
- 参数:anomalies = {{所有异常列表}}
- MCP 按语义相似度聚类,返回异常组
### 步骤 4:输出关联分析结果
对每个异常组输出:
- cluster_id
- 包含的异常列表
- correlation:它们如何关联(同 trace_id / 时间窗 / 语义相似)
- common_root_hypothesis:共同根因假设(一句话)
- confidence
无法关联到任何组的异常,标为 isolated_anomalies。
## 输出格式
严格输出以下 JSON:
{
"correlated_clusters": [
{
"cluster_id": "c1",
"anomalies": ["log_id1", "log_id2"],
"correlation": "...",
"common_root_hypothesis": "...",
"confidence": 0.0
}
],
"isolated_anomalies": ["log_id3"]
}
## 各服务异常列表
{{service_anomalies_json}}
Prompt 模板 3:Bug 根因推断
你是 SRE 团队的 Bug 根因推断助手。你的任务是基于异常识别 + 关联分析,
输出具体的 Bug 假设。假设必须具体到代码层,不能笼统说"服务异常"。
## 输入
1. 各服务异常列表(含 pattern_type/anomaly_signal/log_id)
2. 跨服务关联分析(correlated_clusters + isolated_anomalies)
## 任务
### 步骤 1:对每个异常组(或孤立的 high severity 异常)推断根因
基于异常的 pattern_type 和 anomaly_signal,推断最可能的 Bug。
推断时要结合:
- 异常模式(trend_shift 多半是资源泄漏或配置问题;retry_anomaly 多半是
下游间歇性故障或重试逻辑错误;state_inconsistency 多半是并发/竞态)
- 日志内容(具体数值、错误信息)
- 跨服务关联(哪些服务受影响、传播方向)
### 步骤 2:为每个假设构建证据链
每个假设必须有 3-5 条证据,每条证据引用具体日志行(log_id + message 片段)。
证据要形成逻辑链:现象 → 中间推断 → 根因。
### 步骤 3:给出验证建议
告诉开发者怎么验证这个假设:
- 查哪段代码(具体到模块/函数名,如果能从 logger 名推断)
- 看哪个监控指标(JVM 堆、连接池、缓存命中率)
- 做什么操作能复现或排除
### 步骤 4:标注数据缺口
如果证据不足以确信,说明缺什么数据(比如"需要源码确认""需要某指标的
历史曲线"),这些缺口会引导值班人补数据。
## 输出格式
严格输出以下 JSON:
{
"bug_hypotheses": [
{
"rank": 1,
"bug_hypothesis": "一句话描述 Bug(具体到 X 服务的 Y 模块有 Z 问题)",
"pattern_type": "trend_shift|...",
"evidence_chain": [
"证据1(引用 log_id + 日志内容)",
"证据2",
"证据3"
],
"affected_services": ["service1", "service2"],
"expected_symptom": "如果假设成立,应该还能观察到什么",
"suggested_investigation": [
"1. 查 XXX 代码",
"2. 看 XXX 指标",
"3. 做 XXX 操作"
],
"confidence": 0.0,
"severity": "high|medium|low"
}
],
"data_gaps": ["缺什么数据1", "缺什么数据2"]
}
confidence 标准:
- 0.8-1.0:证据链完整、模式清晰、有历史类似案例
- 0.6-0.79:证据较充分但有一两个缺口
- 0.4-0.59:有方向但证据不足,需人工补数据
- < 0.4:弱假设,仅作参考
severity 标准:
- high:疑似会导致服务故障(OOM、数据错乱、死锁、安全漏洞)
- medium:影响性能或可靠性,但未到故障
- low:值得关注但暂无影响
## 各服务异常列表
{{service_anomalies_json}}
## 跨服务关联分析
{{correlation_result_json}}
效果数据对比
流水线上线后跑了 8 周(2026 年 7 月 14 日 - 9 月 8 日),和基线期(6 月整月 + 7 月前两周的人工审计)做对比。数据来自 ELK、值班系统、GitHub Issue、Slack 消息记录的全量统计。
核心指标对比
| 指标 | 基线期(人工审计) | AI 期(自动化) | 变化 |
|---|---|---|---|
| 日均日志条数 | 1,217,400 条 | 1,224,800 条 | +0.6%(日志量本身在涨) |
| 审计覆盖率(真正读过的日志比例) | 约 5%(脚本过滤+抽样) | 100%(采样后全扫) | +95pp |
| 单次全量审计耗时 | 5 个工作日(2 人) | 27 分钟(凌晨自动跑) | -99.9% |
| 审计频率 | 1 次/周 | 1 次/天 | 7x |
| 隐式异常检出率 | 0%(人眼看不过来) | 91.3% | — |
| Bug 平均潜伏期 | 4.2 天 | 0.5 天 | -88.1% |
| 异常识别准确率 | N/A(人工识别) | 91.3% | — |
| 误报率 | N/A | 4.1% | — |
| 根因推断 Top-1 准确率 | N/A | 67.8% | — |
| 根因推断 Top-3 准确率 | N/A | 88.4% | — |
| SRE 日均审计工时 | 8 小时(2 人×4 小时) | 0.5 小时(看报告) | -93.8% |
| 自动建 Issue 数 | 0(全人工) | 日均 2.3 个 | — |
| 人工兜底触发率 | N/A | 12.4%(confidence < 0.7) | — |
异常识别准确率细分
按异常模式拆开看准确率(人工抽检 320 条标注真值):
| 异常模式 | 样本数 | 识别准确率 | 误报率 | 备注 |
|---|---|---|---|---|
| trend_shift | 68 | 95.6% | 2.9% | 趋势明显,GPT-5 最擅长 |
| retry_anomaly | 54 | 92.6% | 5.6% | 个别正常重试被误标 |
| latency_outlier | 47 | 89.4% | 6.4% | 偶发延迟和持续延迟难分 |
| state_inconsistency | 39 | 87.2% | 7.7% | 业务语义复杂,需业务知识 |
| resource_pressure | 51 | 90.2% | 5.9% | 阈值判断较明确 |
| unusual_pattern | 61 | 88.5% | 8.2% | "从未出现"判断依赖历史数据完整性 |
规律:trend_shift 准确率最高(数值趋势明显),unusual_pattern 和 state_inconsistency 准确率最低(前者依赖历史数据完整性,后者需要业务背景知识)。
准确率随反馈样本积累的变化
| 周次 | 异常识别准确率 | 误报率 | 根因 Top-1 | 累计反馈样本 |
|---|---|---|---|---|
| 第 1 周 | 62.4% | 18.2% | 41.3% | 0 |
| 第 2 周 | 78.1% | 11.4% | 53.7% | 180 |
| 第 4 周 | 86.2% | 6.1% | 61.2% | 720 |
| 第 6 周 | 89.1% | 4.8% | 65.4% | 1180 |
| 第 8 周 | 91.3% | 4.1% | 67.8% | 1640 |
关键观察:准确率随反馈样本积累持续提升,但提升速度在递减(第 1-2 周涨 15.7pp,第 7-8 周涨 2.2pp)。说明 few-shot 样本到 1500 条左右进入收益递减区,后续要继续提升得靠模型升级或更结构化的推理。
SRE 主观满意度
对 6 个 SRE 做了问卷调查(1-5 分):
| 维度 | 基线期评分 | AI 期评分 |
|---|---|---|
| 审计疲劳度(1=极度疲劳,5=轻松) | 1.2 | 4.3 |
| 隐式异常发现信心(1=经常漏,5=从不漏) | 1.0 | 4.1 |
| 根因定位效率(1=慢,5=快) | 1.6 | 3.9 |
| 误报干扰程度(1=严重干扰,5=无干扰) | N/A | 3.7 |
| 整体工作满意度 | 2.1 | 4.4 |
最显著的提升在"隐式异常发现信心"——基线期人工审计根本发现不了 INFO 级隐式异常(评分 1.0),AI 流水线让这件事从"不可能"变成"每天自动做"。
3 个发现的 Bug 详情
这里详细复盘流水线发现的 3 个隐藏 Bug,每个含日志片段、AI 分析过程、人工确认结果。
Bug 1:billing-service 内存泄漏(ConfigCache 的 key 永不淘汰)
日志片段(INFO 级,无告警):
[2026-09-07 08:14:32] INFO com.company.billing.cache.ConfigCache -
cache entry evicted: key=feature_flag_promo_2026,
tenant_config size=482MB, max=512MB, eviction_count_today=478
[2026-09-07 08:14:35] INFO com.company.billing.cache.ConfigCache -
cache entry evicted: key=feature_flag_search_v2,
tenant_config size=482MB, max=512MB, eviction_count_today=479
[2026-09-07 08:14:38] INFO com.company.billing.cache.ConfigCache -
cache entry evicted: key=feature_flag_xxx,
tenant_config size=482MB, max=512MB, eviction_count_today=480
AI 分析过程:
-
第 1 步异常识别:GPT-5 识别为
trend_shift,证据是eviction_count_today从历史均值 3 次/天涨到 480 次/天,且tenant_config size从 8 月 11 日的 280MB 涨到 482MB,接近 512MB 上限。confidence=0.87。 -
第 2 步关联分析:GPT-5 调用
aggregate_by_trace,发现同一时间段 payment-service 有retry_anomaly(重试 billing 调用)、gateway 有latency_outlier(billing 请求 18 秒)。三者通过 trace_id 串联,判定是连锁异常。 -
第 3 步根因推断:GPT-5 输出假设——"ConfigCache 的 tenant_config key 永远不被 LRU 淘汰,导致非 tenant_config 的 key 被反复驱逐,缓存大小持续增长,最终 OOM"。证据是驱逐的总是
feature_flag_*而非tenant_config_*。confidence=0.84。
人工确认结果:
billing-service 负责人按 suggested_investigation 查 ConfigCache 源码,发现:
// ConfigCache.java(问题代码)
public class ConfigCache {
private static final Cache<String, Config> CACHE = Caffeine.newBuilder()
.maximumSize(10000)
.build();
// Bug:tenant_config 的 key 被外部 static 引用持有,
// 导致 Caffeine 的弱引用 key 无法被 GC,永远不淘汰
private static final Map<String, Config> TENANT_CONFIG_REF = new HashMap<>();
public Config get(String key) {
Config config = CACHE.get(key, k -> loadFromDb(k));
if (key.startsWith("tenant_config_")) {
TENANT_CONFIG_REF.put(key, config); // ← 这里持有强引用
}
return config;
}
}
根因确认:TENANT_CONFIG_REF 这个 static Map 持有了所有 tenant_config 的强引用,导致 Caffeine 的弱引用 key 无法被 GC 回收,tenant_config 永远不被淘汰,缓存大小持续增长。
修复:删除 TENANT_CONFIG_REF(它是个调试时遗留的代码,本不该存在),改用 Caffeine 的 recordStats() 做监控。修复 commit:a1b2c3d。
回归验证:修复上线后,下一次日批处理确认 eviction_count 回落到 3 次/天,tenant_config size 稳定在 280MB。Bug 消除。
Bug 2:order-service 竞态条件(并发更新订单状态未加锁)
日志片段(INFO 级,无告警):
[2026-09-07 14:23:01.234] INFO OrderService -
order status update: order_id=ORD_48217,
from=CREATED, to=PAID, thread=pool-1-thread-7
[2026-09-07 14:23:01.235] INFO OrderService -
order status update: order_id=ORD_48217,
from=CREATED, to=PAID, thread=pool-3-thread-2
[2026-09-07 14:23:01.237] INFO InventoryService -
inventory deducted: order_id=ORD_48217,
amount=1, thread=pool-1-thread-7
[2026-09-07 14:23:01.238] INFO InventoryService -
inventory deducted: order_id=ORD_48217,
amount=1, thread=pool-3-thread-2
AI 分析过程:
-
第 1 步异常识别:GPT-5 识别为
state_inconsistency,证据是同一 order_id 在 1 毫秒内被两个线程从 CREATED 更新到 PAID,且库存被扣了 2 次(每次扣 1,共扣 2,但订单只应扣 1)。confidence=0.71(这个模式需要理解业务语义)。 -
第 2 步关联分析:这条异常没有跨服务关联(就是 order-service 内部的竞态),但 GPT-5 调用
get_related_service_logs发现 InventoryService 同时间窗有"库存扣减"日志,确认是 order 的并发更新导致 inventory 被重复调用。 -
第 3 步根因推断:GPT-5 输出假设——"OrderService 的状态更新没有对 order_id 加锁,支付回调并发到达时两个线程同时把 CREATED→PAID,各自触发库存扣减,导致库存被扣 2 次"。confidence=0.71。
人工确认结果:
order-service 负责人查源码:
// OrderService.java(问题代码)
public void handlePaymentCallback(String orderId) {
Order order = orderRepo.findById(orderId);
if (order.getStatus() == OrderStatus.CREATED) {
order.setStatus(OrderStatus.PAID);
orderRepo.save(order); // ← 无锁,并发时会重复执行
inventoryService.deduct(orderId, 1); // ← 被调用 2 次
}
}
根因确认:handlePaymentCallback 没有对 orderId 加分布式锁,支付平台重试回调时两个请求并发到达,都判断 status==CREATED,都执行状态更新和库存扣减。
修复:加 Redis 分布式锁:
public void handlePaymentCallback(String orderId) {
String lockKey = "order:callback:" + orderId;
if (redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
try {
Order order = orderRepo.findById(orderId);
if (order.getStatus() == OrderStatus.CREATED) {
order.setStatus(OrderStatus.PAID);
orderRepo.save(order);
inventoryService.deduct(orderId, 1);
}
} finally {
redisLock.unlock(lockKey);
}
}
}
修复 commit:e5f6g7h。同时修复了已重复扣库存的 17 个订单(人工补偿)。
回归验证:修复上线后,并发回调场景下库存只扣 1 次。Bug 消除。
Bug 3:payment-service 错误的重试逻辑(对 4xx 也重试)
日志片段(INFO 级,无告警):
[2026-09-07 09:15:22.100] INFO PaymentClient -
call billing-service failed, http_status=429,
retrying (attempt 2/5)
[2026-09-07 09:15:23.345] INFO PaymentClient -
call billing-service failed, http_status=429,
retrying (attempt 3/5)
[2026-09-07 09:15:24.612] INFO PaymentClient -
call billing-service failed, http_status=429,
retrying (attempt 4/5)
[2026-09-07 09:15:25.890] INFO PaymentClient -
call billing-service failed, http_status=429,
retrying (attempt 5/5)
[2026-09-07 09:15:27.123] INFO PaymentClient -
call billing-service failed, http_status=429,
retries exhausted, giving up
AI 分析过程:
-
第 1 步异常识别:GPT-5 识别为
retry_anomaly,证据是对 HTTP 429(Too Many Requests)重试了 5 次,而 429 是限流信号,重试只会加剧限流。confidence=0.74。 -
第 2 步关联分析:GPT-5 关联到 billing-service 同时间有
resource_pressure(连接池 active=48/50),说明 billing 被打满。payment 的重试是雪上加霜。 -
第 3 步根因推断:GPT-5 输出假设——"PaymentClient 的重试逻辑对所有非 2xx 状态码都重试,包括 4xx(429/400/404),4xx 是客户端错误不该重试,对 429 重试会导致限流恶化"。confidence=0.68(部分确认——GPT-5 描述的方向对,但具体机制不完全准确)。
人工确认结果:
payment-service 负责人查源码:
// PaymentClient.java(问题代码)
public Response callBilling(Request req) {
for (int i = 0; i < MAX_RETRY; i++) {
Response resp = httpClient.execute(req);
if (resp.getStatus() >= 200 && resp.getStatus() < 300) {
return resp;
}
// Bug:所有非 2xx 都重试,包括 4xx
log.info("call billing-service failed, http_status={}, retrying", resp.getStatus());
}
throw new RuntimeException("retries exhausted");
}
根因确认:重试逻辑只判断"非 2xx 就重试",没有区分 4xx(不该重试)和 5xx(可以重试)。429 重试 5 次让 billing 的限流雪上加霜。
修复:只对 5xx 和网络超时重试,4xx 直接失败:
public Response callBilling(Request req) {
for (int i = 0; i < MAX_RETRY; i++) {
try {
Response resp = httpClient.execute(req);
int status = resp.getStatus();
if (status >= 200 && status < 300) return resp;
if (status >= 400 && status < 500) {
// 4xx 不重试,直接抛出
throw new ClientException("billing returned " + status);
}
// 仅 5xx 重试
log.info("call billing-service failed, http_status={}, retrying", status);
} catch (SocketTimeoutException e) {
log.info("call billing-service timeout, retrying");
}
}
throw new RuntimeException("retries exhausted");
}
修复 commit:i8j9k0l。
回归验证:修复上线后,429 不再被重试,billing 的限流压力下降。Bug 消除。
踩坑经验
流水线开发和迭代过程中踩了 5 个主要的坑,每个都影响了准确率或可用性。
坑 1:日志采样丢失关键信息
现象:上线第 1 周,异常识别准确率只有 62%,抽检发现大量"漏检"——某些真实异常的日志没被采样到,GPT-5 根本没看到。最典型的例子是 order-service 的竞态条件:两条并发日志间隔 1 毫秒,但采样时按"每服务每小时随机抽 3 条 INFO",这两条刚好都没被抽中。
根因:采样策略是"按服务 + 按小时随机抽样",对 INFO 日志一视同仁地抽 0.05%。但有些异常信号是"多条日志的时序关系"(比如竞态条件要看到两条几乎同时的日志),随机抽样会打散这种关系。
解法:
- 异常关键词全量保留:INFO 日志里命中异常关键词表的(retry/evict/timeout 等)100% 保留,不抽样。这把"含异常信号的 INFO"从 0.05% 采样率提升到 100% 全量。
- 相邻日志连带保留:对命中关键词的日志,前后 5 秒内同 trace_id 的日志也连带保留,保住时序关系。
- 采样窗口对齐:INFO 抽样改成按"5 分钟窗口"而非"每小时窗口",每 5 分钟内至少抽 1 条,避免某段时间完全空白。
# 改进后的采样逻辑(伪代码)
def sample_logs(logs):
sampled = []
for log in logs:
if log.level in ["FATAL", "ERROR"]:
sampled.append(log) # 全量
elif log.level == "WARN":
if hash(log.log_id) % 10 == 0: # 10% 抽样
sampled.append(log)
elif log.level == "INFO":
if has_anomaly_keyword(log.message):
sampled.append(log) # 关键词命中全量
# 连带保留前后 5 秒同 trace_id 的日志
sampled.extend(get_related_within_5s(log.trace_id, log.timestamp))
elif hash(log.log_id) % 2000 == 0: # 0.05% 抽样
sampled.append(log)
return sampled
效果:漏检率从 38% 降到 8.7%(准确率从 62% 升到 78%——这一项改动贡献了 16pp 的提升)。
坑 2:语义歧义导致误报——"retry"不一定是异常
现象:误报率 18% 里,最大的一类是"retry 关键词误报"。GPT-5 看到日志里含 "retry" 就标 retry_anomaly,但很多 retry 是正常设计:
- 消息队列消费失败的 retry 是预期的容错机制
- HTTP 客户端的 connection retry(连接建立阶段重试)和 request retry(请求阶段重试)语义不同
- 定时任务的 retry 是任务调度正常行为
GPT-5 把这些正常 retry 都标成异常,日均产生 20+ 条误报,开发者抱怨"狼来了"。
根因:异常关键词表太粗,"retry" 命中了大量正常 retry 日志,GPT-5 缺乏区分"正常 retry"和"异常 retry"的判据。
解法:
- 关键词细化:把 "retry" 细化成 "retry attempt [3-9]"(重试次数 ≥3 才算异常)、"retry exhausted"(重试耗尽)、"retrying 4[0-9][0-9]"(对 4xx 重试),正常 retry 不命中。
- Prompt 加判据:在异常识别 Prompt 里明确告诉 GPT-5 "重试次数 < 3 是正常的,>= 3 才算异常;对 4xx 重试才算异常,5xx 重试是正常的"。
- 历史频次基线:MCP 维护每个服务每个关键词的历史频次基线,GPT-5 看到的关键词日志附带
freq_baseline(历史频次)和current_freq(当天频次),让它判断"这个 retry 频次是否异常"而非"有没有 retry"。
// 改进后的日志附带频次基线
{
"log_id": "...",
"message": "retry attempt 5 succeeded",
"freq_baseline": {
"retry_attempt_5": {"history_avg_per_day": 0.2, "today_count": 8}
}
}
效果:retry 相关误报从日均 20 条降到 3 条,误报率从 18% 降到 11%。
坑 3:traceId 断链——跨服务关联失败
现象:第 2 步跨服务关联分析,经常出现"traceId 断链"——调用链在某 个服务断掉,GPT-5 无法把上下游异常关联起来。抽检发现 38% 的跨服务异常因为 traceId 断链被当成孤立异常处理,根因推断准确率上不去。
根因:traceId 断链有三个原因:
- 异步调用丢 traceId:线程池/消息队列异步执行时,traceId 没有通过 MDC(Mapped Diagnostic Context)传递,子线程的日志没有 traceId
- 第三方调用不带 traceId:调外部支付/短信 API 时,对方日志当然没有我们的 traceId
- 日志框架配置不一致:有的服务用 Logback 的 MDC,有的用 Log4j2 的 ThreadContext,traceId 字段名不统一(
trace_idvstraceIdvsx-request-id)
解法:
- MCP 做模糊匹配:
aggregate_by_trace工具在 traceId 断链时,按"时间窗 + service + tenant_id"做模糊匹配,把疑似同一调用链的日志关联起来。即使 traceId 断了,如果两条日志时间窗重合 + tenant_id 相同,大概率是同一调用链。 - 统一日志框架配置:全集群统一用 Logback + MDC,traceId 字段名统一
trace_id,异步调用强制通过MDC.getCopyOfContextMap()传递。 - traceId 断链标注:MCP 在无法关联时标注
trace_broken: true,GPT-5 看到这个标注会对该异常降权处理(不强行关联,避免错误关联)。
# MCP 模糊匹配逻辑(伪代码)
def aggregate_by_trace(trace_ids):
result = []
for tid in trace_ids:
chain = get_logs_by_trace_id(tid) # 精确匹配
if len(chain.services) < expected_chain_length(tid):
# traceId 断链,尝试模糊匹配
for log in chain:
related = get_logs_by_time_and_tenant(
time_window=log.timestamp ± 5s,
tenant_id=log.tenant_id,
exclude_service=log.service
)
chain.extend(related) # 补全断链部分
chain.trace_broken = True
result.append(chain)
return result
效果:跨服务关联成功率从 62% 升到 84%,根因推断 Top-1 准确率从 53% 升到 68%。
坑 4:超大日志超 token 限制——单条日志 8 万 token
现象:一次批处理中,order-service 的一条 ERROR 日志含完整堆栈,堆栈有 1200 行(嵌套异常链),单条日志占 8 万 token。GPT-5 那一批总共 70 条日志,这一条就占了 60% 上下文,其余 69 条日志的异常信号被淹没,GPT-5 漏检了其中 2 条真异常。
根因:日志裁剪不够狠,堆栈折叠只对重复堆栈生效,对嵌套异常链(Caused by 链)没折叠,导致超长堆栈原样进入 GPT-5 上下文。
解法:
- 堆栈折叠增强:标准化层对所有堆栈做折叠——第一行 + Caused by 链的每个第一行 + 末尾 3 行 + 总行数。1200 行压成 20 行。
- 单条日志 token 上限:标准化层对每条日志设 2000 token 上限,超出的截断并标注
truncated: true, original_size: XXXX tokens。GPT-5 看到截断标注会调 MCP 拉完整日志(按需)。 - 批次 token 预算:每批日志总 token 不超过 3 万(70 条 × 平均 400 token),超出时分两批处理。
# 堆栈折叠逻辑(伪代码)
def fold_stacktrace(stack_lines):
if len(stack_lines) <= 20:
return stack_lines # 短堆栈不折叠
folded = []
folded.append(stack_lines[0]) # 第一行
# 提取 Caused by 链的每个第一行
for i, line in enumerate(stack_lines):
if line.startswith("Caused by:"):
folded.append(line)
folded.extend(stack_lines[-3:]) # 末尾 3 行
folded.append(f"... [{len(stack_lines)} lines total, folded]")
return folded
效果:单批最大 token 从 9.8 万降到 2.8 万,超长堆栈场景的漏检率从 28% 降到 6%。
坑 5:误报疲劳——开发者开始忽略 AI 报告
现象:上线第 2 周,误报率 11%,日均 3-4 条误报。听起来不高,但累积效应可怕——开发者每天早上收到 3-4 条"疑似 Bug"通知,连续一周后发现其中 30% 是误报,开始整体忽略 AI 报告。第 3 周的一条真 Bug(内存泄漏)的 Issue 在 36 小时后才被开发者看到,因为开发者已经习惯性忽略 #log-anomaly-high 频道。
根因:这是 AI 检测系统的"狼来了"问题。即使误报率只有 10%,如果每天报 4 条、其中 1 条是误报,开发者很快会形成"AI 报告大部分不准"的认知,真 Bug 也会被忽略。误报的代价不是"处理误报的时间",而是"真 Bug 被忽略的风险"。
解法:
- 降误报率到 5% 以下:通过坑 1(采样改进)+ 坑 2(关键词细化)+ 反馈样本积累,把误报率从 11% 压到 4%。
- 分级通知:只有 high severity + confidence > 0.8 的异常才 @人,medium 和 low 只发到频道不 @人。开发者每天只被 @ 0-1 次,不会被轰炸。
- 误报归因:每条误报都记录"为什么误报",每周复盘 top 3 误报模式,针对性改 Prompt。比如发现"消息队列 retry"是高频误报源,就在 Prompt 里加"消息队列消费 retry 是正常的,不要标异常"。
- 准确率看板:给开发者看一个准确率看板——"本月 AI 报告 30 条异常,其中 27 条是真 Bug,准确率 90%"。让开发者对 AI 有信心,而不是凭印象觉得"不准"。
效果:开发者响应时间从峰值 36 小时降到 4 小时以内,准确率看板让开发者重新开始重视 AI 报告。
部署和运维
流水线跑起来后的日常运维要点。
监控 AI 流水线自身
"谁来看着看日志的 AI"是个绕不开的问题。我们给流水线自身建了一套监控:
| 指标 | 阈值 | 告警动作 |
|---|---|---|
| 日批处理是否按时触发 | 2:30 未触发 | 告警到 #ai-log-ops,定时任务可能挂了 |
| 批处理总耗时 | > 35 分钟 | 告警,可能 GPT-5 API 慢或日志量异常 |
| GPT-5 API 调用失败率 | > 5% | 立即告警,可能 API 配额用完 |
| 日志分析 MCP 调用超时率 | > 20% | 告警,ELK 可能慢了 |
| 单日异常报告数突变 | 24h 变化 > 5 条 | 告警,可能是某服务出了大问题或采样策略变了 |
| 人工兜底触发率 | > 20% | 告警,说明 GPT-5 准确率下降,需要补样本 |
| 误报率突变 | 7 天均值变化 > 5pp | 告警,需要复盘近期 Prompt 改动 |
Prompt 版本管理
- Prompt 存在独立 Git 仓库
ai-log-detection-prompts,每个 Prompt 一个.md文件 - 修改走 PR 流程,PR 描述必须说明"改了什么、为什么改、预期影响"
- 上线前在影子环境跑 100 条历史日志做回归测试,准确率下降超过 2pp 不允许合并
- 线上 Prompt 版本号记录在每条异常报告里,出问题可以回滚
异常报告的日常消费流程
为了让 AI 产出的异常报告真正被消费,我们规范了值班人和开发者的日常流程:
| 时间 | 动作 | 责任人 |
|---|---|---|
| 2:00-2:30 | AI 批处理自动跑,产出异常报告 | 系统自动 |
| 8:30 | 值班人查看 #log-anomaly-high 频道,确认 high severity 异常 | 值班 SRE |
| 9:00 | 值班人把 high severity 异常转给对应服务负责人 | 值班 SRE |
| 9:00-18:00 | 服务负责人按 suggested_investigation 验证 Bug 假设 | 服务负责人 |
| 当日 | 确认/否定假设,在 Issue 里回复反馈 | 服务负责人 |
| 修复后 | 提交修复 commit,标记 Issue resolved | 服务负责人 |
| 次日 2:00 | 下一次批处理验证异常信号是否消失 | 系统自动 |
这套流程让"AI 发现异常 → 人验证修复 → AI 回归验证"形成闭环,Bug 从发现到修复的平均周期控制在 1.5 天以内。
成本核算
8 周运行的成本数据:
| 成本项 | 月均花费 | 备注 |
|---|---|---|
| GPT-5 API 调用 | ¥5,200 | 日均 38 服务 × 平均 6K token × 单价 |
| ELK 额外查询负载 | ¥450 | MCP 查询增加的 ELK 资源开销 |
| Kafka 集群 | ¥0 | 复用现有集群 |
| PostgreSQL | ¥0 | 复用现有实例 |
| K8s 资源(MCP + consumer) | ¥650 | 3 个 MCP × 2 副本 + 4 个 consumer pod |
| 合计 | ¥6,300/月 | — |
对比收益:
- SRE 审计工时从 8 小时/天降到 0.5 小时/天,节省 7.5 小时/天 × 22 天 = 165 小时/月
- 按 SRE 工时成本 ¥200/小时算,节省 ¥33,000/月
- 净收益 ¥26,700/月,ROI 约 4.2x
还不算 Bug 早发现避免的故障损失(如内存泄漏 8 天潜伏期那次事故的直接成本 ¥38 万,AI 流水线能把潜伏期压到 0.5 天,避免类似事故)。
经验总结
8 周的实践,3 条最关键的经验:
经验 1:日志异常检测的核心是"语义异常",不是"数值异常"
传统日志告警和 ML 异常检测都只能处理"数值异常"——某个指标超阈值、某个计数突增。但真正藏得深的 Bug,信号往往是"语义异常"——日志的级别是 INFO,文字也没出现 error,但语义上透露出系统正在出问题:
- "重试第 5 次成功"——重试 5 次本身没报错,但语义上说明下游间歇性不可用
- "缓存驱逐频次 480 次/天"——驱逐是正常机制,但频次语义上异常
- "订单 PAID 但未扣库存"——两个状态各自正常,但组合起来语义矛盾
这种语义判断只有 LLM 做得了。GPT-5 的价值不在"读得快"(ELK 自己读得更快),而在"读得懂"——能理解日志的语义、能发现时序模式、能把多条日志的语义关联起来。这是规则引擎和 ML 模型永远做不到的。
但反过来,LLM 也做不了规则能稳定做的事——比如"日志级别是 ERROR 就告警"这种确定性逻辑,让 GPT-5 做反而不如写死在告警系统。所以我们的设计是"告警系统做数值异常,GPT-5 做语义异常",各司其职,互补不重叠。
经验 2:采样是准确率和成本的平衡术
120 万条日志全量喂给 GPT-5 既不可能(token 爆炸)也没必要(99.93% 是噪音)。但采样又不能太狠——太狠会漏掉关键信号(坑 1 的竞态条件日志被采样打散)。
采样的设计哲学是"分层 + 关键词 + 时序保留":
- ERROR/FATAL 全量——这些少且重要
- 含异常关键词的 INFO 全量——异常信号的载体
- 普通 INFO 抽样——只为发现 unusual_pattern
- 关键词命中的日志连带保留前后时序——保住时序关系
采样的目标是"用 0.23% 的日志覆盖 99% 以上的异常信号"。这个比例不是拍脑袋定的,是跑了一周对比"全量人工标注 vs 采样后 GPT-5 识别",找到的漏检率 < 10% 的临界点。采样率太高浪费成本,太低漏检,必须找到平衡点。
经验 3:反馈闭环是流水线从"能用"到"好用"的唯一路径
第 1 周的准确率(62%)只能叫"勉强能用",真正让它变成"好用"的是 8 周积累的 1640 条反馈样本。没有反馈闭环,LLM 的判断永远停留在 zero-shot 水平;有了反馈闭环,它才成了一个能持续学习的系统。
反馈闭环的关键设计是"低置信度走人工 + 人工结果回流"。这既保证了线上安全(低置信度不直接落地),又保证了训练数据质量(人工标注的都是 GPT-5 拿不准的难例,价值最高)。
下一步
流水线跑稳后,下一步的两个方向:
-
从日级批处理到小时级流处理:现在一天跑一次,Bug 最长潜伏 24 小时。下一步打算做小时级流处理——每小时的日志攒一批跑一次,把潜伏期压到 1 小时以内。挑战是成本控制(小时级跑 24 次 vs 日级跑 1 次,API 费用涨 24 倍),需要更激进的采样和缓存。
-
从检测到自愈:现在流水线的终点是"建 Issue + 发通知 + 给验证建议",人还是要去查代码修复。下一步想接 K8s MCP,让 GPT-5 对"内存泄漏重启 Pod""连接池满扩容"这种确定性处置直接执行(带审批),把修复时间从小时级压到分钟级。
-
从单集群到多租户:现在流水线只服务我们自建集群,下一步打算开放给平台上的租户用——每个租户的日志走独立的采样和分析 pipeline,异常报告按租户隔离。挑战是多租户隔离下的成本控制和 Prompt 通用性。
日志异常检测只是 SRE 智能化的一个场景,但它证明了"LLM + MCP"的组合在日志分析场景是可行的——只要设计好采样策略、做好反馈闭环、控制好上下文,AI 能从海量日志里把藏得最深的 Bug 挖出来,让人去做只有人能做的判断和决策。
这套流水线的所有 Prompt 模板、MCP 工具定义、采样策略配置都已在内部开源仓库 ai-log-detection 归档,欢迎同团队复用和改进。