本教程带你从零构建一个名为 log-rca(Log Root Cause Analysis)的 Claude Code Skill。它能读取一份服务日志,自动提取异常事件、关联时间窗口、给出排序后的根因假设并产出结构化分析报告。整篇教程聚焦一个多数已有 Skill 教程都没碰过的应用场景——生产日志根因定位,并完整演示官方推荐的"带可执行代码的 Skill"四要素:工具脚本(utility scripts)、可验证中间输出(verifiable intermediate outputs)、渐进披露的参考知识库、模板化的最终产物。
适用人群:已跑通过至少一个 Claude Code 项目、了解 SKILL.md 基本概念、希望构建能真正在生产环境复用的复杂 Skill 的中高级开发者与 SRE。
allowed-tools 预批准、实时变更检测均依赖此版本)python3(3.9+)与 gitgit 历史的项目目录用于测试(任何项目均可)把同一段分析步骤反复粘进对话框,是创建 Skill 最典型的信号。根因分析尤其符合:每次都要"先抽异常、再按时间聚类、再查错误码含义、再套报告模板"。CLAUDE.md 适合写事实("我们的网关是 Nginx 1.24"),而 Skill 适合写流程——而且它的正文只有在被激活时才进入上下文,长篇参考知识库平时零成本。
一个能复用的分析型 Skill,本质是把"确定性计算"交给脚本、"模糊判断"交给模型、"领域知识"交给参考文件、"格式规范"交给模板。四者各司其职:
| 层 | 文件 | 职责 | 由谁执行 |
|---|---|---|---|
| 控制层 | SKILL.md |
工作流编排、假设生成与排序 | Claude |
| 计算层 | scripts/extract.py |
正则抽取、时间聚类、统计 | Python(确定性) |
| 知识层 | references/patterns.md |
错误码含义、常见根因对照表 | Claude 按需读取 |
| 表现层 | templates/report.md |
报告骨架与字段约束 | Claude 填充 |
关键设计原则来自官方最佳实践:默认假设 Claude 已经很聪明——它知道什么是堆栈、什么是超时,所以参考文件只放它不可能猜到的内部知识(自家错误码、本集群拓扑约定),不要写它已经懂的常识。
flowchart TD
U[用户: /log-rca app.log] --> SK[SKILL.md 控制层]
SK -->|动态注入| EX[scripts/extract.py 计算层]
EX -->|JSON 中间产物| SK
SK -->|按需读取| REF[references/patterns.md 知识层]
REF -->|错误码语义| SK
SK -->|填充| TPL[templates/report.md 表现层]
TPL --> R[结构化根因报告]
EX -.可验证.-> CHK{中间产物可复算?}
CHK -->|是| R
个人级 Skill 放在 ~/.claude/skills/ 下,对所有项目生效;项目级放在 .claude/skills/ 下。这里做项目级,方便随仓库分发给团队。
mkdir -p .claude/skills/log-rca/{scripts,references,templates}
cd .claude/skills/log-rca
目录骨架:
log-rca/
├── SKILL.md # 主控流程(必需)
├── scripts/
│ └── extract.py # 日志解析引擎
├── references/
│ └── patterns.md # 错误码知识库
└── templates/
└── report.md # 报告模板
预期结果:在当前会话内,Claude Code 的实时变更检测会立刻发现新 Skill,无需重启。若你是会话启动后首次创建顶层的
.claude/skills/目录(目录此前不存在),则需要重启一次 Claude Code 让它开始监听。
创建 SKILL.md 的 frontmatter。frontmatter 是 Claude Code 在加载正文前先读取的元数据,控制发现、调用、权限与执行位置:
---
name: log-rca
description: 对一份服务日志做根因分析,抽取异常事件、按时间窗口聚类、给出排序后的根因假设并产出报告。当用户贴入或指向一份 .log/.out 文件、说"分析这份日志""为什么挂了""排查一下报错"时使用。
allowed-tools:
- Bash(log-rca:*)
- Read
disable-model-invocation: false
---
逐字段说明:
name:小写、字母数字与连字符、最长 64 字符、目录内唯一。省略时回退到父目录名,但显式写出更稳健(重命名目录不会破坏 /log-rca 命令)。description:整个 Skill 最重要的字段。它驱动自动调用——Claude 用它判断"现在该不该加载这个 Skill"。务必写清楚做什么 + 何时触发,并枚举用户可能的口语化说法("为什么挂了""排查报错")。allowed-tools:Skill 激活期间自动批准的工具,免去逐次确认。Bash(log-rca:*) 用了 Claude Code 的命令前缀限定语法,意为"只自动批准注释标记为 log-rca 的 Bash 调用"——这是最小权限原则,比直接放开整个 Bash 安全得多。disable-model-invocation: false:允许 Claude 自动触发(默认行为)。若是高风险动作如部署,应设为 true,只允许用户手动 /log-rca 调用。常见问题:Skill 不触发?九成是
description写得太泛(如"分析日志")或漏掉了用户实际会说的触发短语。把它写成包含动词 + 对象 + 场景的完整句。
frontmatter 之后就是正文。正文采用官方推荐的条件工作流模式(conditional workflow pattern)——用编号步骤告诉 Claude 在什么条件下做什么,而不是堆细节。
# 日志根因分析
你是一位资深 SRE。下面是一份待分析日志的预处理结果,以及分析步骤。
## 预处理结果(脚本已抽取,可直接信任)
!`python3 "$SKILL_DIR/scripts/extract.py" "$ARGUMENTS" 2>/dev/null || echo "EXTRACT_FAILED: 请确认日志文件路径正确"`
## 分析步骤
1. **校验中间产物**:若上方出现 `EXTRACT_FAILED`,停止并向用户索要正确路径;否则继续。
2. **识别主异常**:在"异常事件"列表中,找出出现频次最高或最先爆发的那一类,作为主线索。
3. **关联时间窗口**:检查主异常前后 ±60 秒内的 WARNING/慢查询/资源指标,建立因果候选链。
4. **查证错误码**:对每个未知错误码,读取 `references/patterns.md` 获取其语义与常见根因。**只在这一步需要时才读**,不要预先加载。
5. **生成假设**:给出 1–3 条根因假设,按"证据充分度"从高到低排序,每条标注依据的事件编号。
6. **产出报告**:严格按 `templates/report.md` 的结构填充并输出。
## 约束
- 中间产物的数字是脚本算出来的,不要自己重新数日志行——那是确定性结果,直接引用。
- 若证据不足以支撑某假设,明确标注"证据不足",不要编造。
- 报告中所有时间戳必须能在原始日志中复核。
这里有两个关键技术点:
动态上下文注入:!command` 语法。Claude Code 在把 SKILL.md 交给模型之前,会先执行该命令并把整行替换为输出。所以模型看到的不是命令本身,而是已经算好的中间结果——它的推理因此"接地"于真实数据,而不是靠猜。变量$SKILL_DIR是 Skill 自身目录的绝对路径,$ARGUMENTS是/log-rca` 后面用户输入的全部参数。
为什么把抽取交给脚本:这正是"可验证中间输出"的精髓。日志里有多少条 ERROR、哪个时间点爆发、哪条堆栈出现最频繁——这些是确定性计算,让 Python 算远比让模型"数"可靠(模型数行数经常出错)。脚本输出 JSON,模型只负责"解读",分工明确,结果可复算、可审计。
常见问题:
$SKILL_DIR不展开?确认 Claude Code 版本 ≥ v2.1.145。旧版本可改用相对路径python3 scripts/extract.py ...并保证工作目录正确。
这是整个 Skill 的"确定性引擎"。它的设计目标只有一个:把非结构化日志变成结构化、可复核的中间产物。
#!/usr/bin/env python3
"""log-rca 计算层:抽取异常、按时间聚类、统计堆栈签名。
输出一段 Markdown,供 SKILL.md 动态注入。刻意不输出原始敏感字段。"""
import re, sys, json
from collections import Counter, defaultdict
from datetime import datetime
if len(sys.argv) < 2:
print("EXTRACT_FAILED: 缺少日志文件路径参数"); sys.exit(0)
path = sys.argv[1]
TS = re.compile(r'(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})')
LEVEL = re.compile(r'\b(ERROR|FATAL|WARN(?:ING)?)\b', re.I)
try:
lines = open(path, encoding='utf-8', errors='replace').read().splitlines()
except OSError as e:
print(f"EXTRACT_FAILED: {e}"); sys.exit(0)
events, signatures = [], Counter()
for ln in lines:
m_ts, m_lv = TS.search(ln), LEVEL.search(ln)
if not (m_ts and m_lv): continue
level = m_lv.group(1).upper()
if level.startswith("WARN"): continue # 先聚焦 ERROR/FATAL
ts = m_ts.group(1).replace("T", " ")
# 堆栈签名:去掉数字/路径/变量,保留异常类型
sig = re.sub(r'0x[0-9a-f]+|\d+|/[\w./\-]+|"[^"]*"', '', ln)
sig = re.sub(r'\s+', ' ', sig).strip()[:80]
signatures[sig] += 1
events.append({"ts": ts, "level": level, "sig": sig})
# 时间聚类:按分钟桶统计 ERROR 数量,找爆发点
buckets = Counter()
for e in events:
buckets[e["ts"][:16]] += 1 # 截到分钟
burst = buckets.most_common(3)
print(f"## 预处理摘要\n- 解析行数: {len(lines)}")
print(f"- ERROR/FATAL 事件: {len(events)}")
print(f"- 堆栈签名种类: {len(signatures)}")
print("\n### Top 堆栈签名(出现次数)")
for sig, n in signatures.most_common(5):
print(f"- ({n}x) `{sig}`")
print("\n### 异常爆发时间窗(按分钟)")
for minute, n in burst:
print(f"- `{minute}` — {n} 条")
print("\n### 前 10 条异常事件")
for e in events[:10]:
print(f"- [{e['ts']}] {e['level']} — {e['sig']}")
赋予执行权限(动态注入走的是 python3 调用,不强制可执行,但养成习惯):
chmod +x scripts/extract.py
手动验证中间产物(这是 Skill 开发的核心纪律——先确保脚本能独立跑通):
echo '2026-06-15T10:00:01 ERROR NullPointerException at UserService.java:42
2026-06-15T10:00:01 ERROR NullPointerException at UserService.java:42
2026-06-15T10:00:03 ERROR ConnectionRefused db.internal:5432' > /tmp/sample.log
python3 scripts/extract.py /tmp/sample.log
预期结果:输出一段 Markdown 摘要,包含事件数 3、签名种类 2、爆发窗
2026-06-15 10:00共 3 条,以及前几条事件列表。脚本永远以exit 0结束并把错误写进正文(EXTRACT_FAILED: ...),这样 SKILL.md 的步骤 1 能稳定捕获——绝不要让脚本抛非零退出码,否则动态注入会中断整个 Skill 加载。常见问题:日志时间戳格式千奇百怪?
TS正则只覆盖了最常见的 ISO 与空格分隔两种。针对自家日志格式扩展正则即可,这恰恰是"内部知识"该沉淀进脚本而非交给模型猜的地方。
参考文件体现渐进披露:它平时不进上下文,只有 SKILL.md 步骤 4 明确要求查错误码时才被读取。所以可以放心写得详尽——这正是放领域知识的正确位置。
# 错误码与常见根因对照表
> 本文件仅在被需要时读取。新增内部错误码时追加到此。
## 数据库相关
| 现象 | 常见根因 | 首要排查 |
|---|---|---|
| ConnectionRefused | DB 进程退出 / 端口被防火墙拦 | `systemctl status postgresql`、安全组规则 |
| ConnectionTimeout | 网络抖动 / 连接池耗尽 | 连接池监控、`netstat -an \| grep TIME_WAIT` |
| too many connections | 上游突发流量 / 连接泄漏 | 慢查询日志、连接数趋势图 |
## 应用层
| 现象 | 常见根因 | 首要排查 |
|---|---|---|
| NullPointerException | 空值未校验 / 上游返回结构变更 | 对应行的上游调用链、最近发布记录 |
| OutOfMemoryError | 堆内存泄漏 / 大对象未释放 | 堆 dump、GC 日志 |
## 基础设施
| 现象 | 常见根因 | 首要排查 |
|---|---|---|
| 502/504 网关 | 后端无响应 / 上游超时 | 上游健康检查、网关 access log |
| disk full | 日志/临时文件未轮转 | `df -h`、crontab 中的清理任务 |
官方最佳实践反复强调一条铁律:避免深层嵌套引用。参考文件最多一层,且文件较长时用目录(TOC)组织。本文件用分类表格替代长段落,模型扫一眼就能定位。
常见问题:把参考写太长会拖慢吗?不会——只要它不被无谓读取。SKILL.md 的步骤 4 写明"只在这一步需要时才读",模型就会按需加载。真正拖慢的是把大段参考直接塞进 SKILL.md 正文,因为它一旦加载就跨多轮常驻。
模板模式(template pattern)让最终产物的格式稳定、可被下游脚本解析。模板里只放结构与约束,不放内容。
# 根因分析报告
**分析对象**:{{文件名}} **分析时间**:{{当前会话时间}} **事件总数**:{{N}}
## 1. 主异常
- 类型:{{最频/最早爆发的异常签名}}
- 爆发窗口:{{起止时间}},峰值 {{N}} 条/分钟
## 2. 因果候选链
{{按时间排列的关键事件序列,每条带事件编号与时间戳}}
## 3. 根因假设(按证据充分度排序)
### 假设 1:{{一句话根因}} — 置信度 高/中/低
- 依据:事件 #{{n}}, #{{m}}
- 反证(若有):{{...}}
## 4. 建议排查动作
1. {{可立即执行的第一步}}
2. {{...}}
## 5. 复核清单
- [ ] 所列时间戳均可在原日志中检索到
- [ ] 每条假设标注了依据事件
- [ ] 证据不足处已明确标注
设计要点:第 5 节"复核清单"是反馈循环的体现——它强迫模型在交付前自检,把"可验证"从中间产物延伸到最终报告。
构造一份贴近真实的故障日志(连接池耗尽导致服务雪崩):
cat > /tmp/incident.log <<'EOF'
2026-06-15T14:00:00 INFO starting worker pool size=50
2026-06-15T14:02:11 WARN slow query 3200ms on orders
2026-06-15T14:02:12 WARN slow query 4100ms on orders
2026-06-15T14:02:30 ERROR ConnectionTimeout db.internal:5432
2026-06-15T14:02:30 ERROR ConnectionTimeout db.internal:5432
2026-06-15T14:02:31 ERROR ConnectionTimeout db.internal:5432
2026-06-15T14:02:45 ERROR NullPointerException at OrderService.java:118
2026-06-15T14:03:01 FATAL OutOfMemoryError Java heap space
EOF
在 Claude Code 中输入:
/log-rca /tmp/incident.log
预期结果:Claude 先在内部执行
extract.py(你会在工具调用区看到一次带log-rca注释的 Bash 调用,因allowed-tools预批准而无需确认),随后基于注入的摘要推理。报告应将 ConnectionTimeout 在 14:02 的爆发 列为主异常,关联紧随其后的NullPointerException与OutOfMemoryError,给出类似"数据库连接超时 → 请求堆积 → 堆内存耗尽雪崩"的因果链,并建议先查数据库连接池与慢查询。
调试中间产物:临时把动态注入行改成 !cat /tmp/last_extract.md``,手动跑一次脚本把结果存进去,就能反复调试推理逻辑而不必每次重跑解析。
触发调优:若 Skill 触发太频繁(用户只是提了"日志"二字就被加载),在 description 里收紧条件、加入更具体的宾语;若触发不够,补充用户口语("为什么挂了""5xx 飙升""服务起了吗")。description 被 Claude Code 自动截断到约 1024 字符,超长部分不参与匹配,把触发短语放在前部。
分发:把整个 .claude/skills/log-rca/ 目录提交到仓库即可被团队共享;若想跨项目复用,移到 ~/.claude/skills/。要进一步打包成可独立分发的插件(同时捆绑 agents/hooks/MCP),在 Skill 目录加 .claude-plugin/plugin.json,即可作为 <name>@skills-dir 插件加载。
| 维度 | 评估 |
|---|---|
| 适合的训练营类型 | SRE/运维训练营、AI 工程实战营、企业内部研发提效培训 |
| 学员基础要求 | 熟悉 Python 基础、读过至少一份 SKILL.md、理解正则与 JSON |
| 预计教学时长 | 授课 45 分钟 + 动手 60 分钟 + 复盘 15 分钟,共约 2 课时 |
| 可拓展练习 | ① 把 extract.py 改为支持 JSON 结构化日志;② 增加 references/metrics.md 关联 Prometheus 指标;③ 用 context: fork 让分析在隔离子 agent 中跑,避免污染主上下文 |
| 评估方式 | 给定一份隐藏答案的故障日志,以报告是否锁定正确根因 + 复核清单全部可验证为评分标准 |
code.claude.com/docs/en/skills,目录结构、frontmatter 与动态注入的权威来源platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices,本教程四要素(工具脚本、可验证中间输出、渐进披露、模板)的原始出处context: fork、paths 自动激活、hooks 生命周期等高级项/code-review、/debug、/run、/verify 等内置 Skill 是学习"工作流模式"与"反馈循环"的最佳范例,可用 /run-skill-generator 录制自家项目的运行配方!cmd``)与实时变更检测的官方权威说明.claude 目录下 skills/commands/agents/hooks 的层级覆盖优先级与团队分发实践[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
