<!-- title: 构建日志根因分析Skill实战 -->

# 构建日志根因分析 Skill 实战：脚本+参考+模板三层架构

## 概述

本教程带你从零构建一个名为 `log-rca`（Log Root Cause Analysis）的 Claude Code Skill。它能读取一份服务日志，自动提取异常事件、关联时间窗口、给出排序后的根因假设并产出结构化分析报告。整篇教程聚焦一个多数已有 Skill 教程都没碰过的应用场景——**生产日志根因定位**，并完整演示官方推荐的"带可执行代码的 Skill"四要素：**工具脚本（utility scripts）、可验证中间输出（verifiable intermediate outputs）、渐进披露的参考知识库、模板化的最终产物**。

**适用人群**：已跑通过至少一个 Claude Code 项目、了解 SKILL.md 基本概念、希望构建能真正在生产环境复用的复杂 Skill 的中高级开发者与 SRE。

## 前置条件

- Claude Code v2.1.145 或更高（本教程用到的动态上下文注入、`allowed-tools` 预批准、实时变更检测均依赖此版本）
- 本地装有 `python3`（3.9+）与 `git`
- 一个带 `git` 历史的项目目录用于测试（任何项目均可）
- 大约 60–90 分钟动手时间

## 为什么用 Skill 而不是直接问 Claude

把同一段分析步骤反复粘进对话框，是创建 Skill 最典型的信号。根因分析尤其符合：每次都要"先抽异常、再按时间聚类、再查错误码含义、再套报告模板"。CLAUDE.md 适合写**事实**（"我们的网关是 Nginx 1.24"），而 Skill 适合写**流程**——而且它的正文只有在被激活时才进入上下文，长篇参考知识库平时零成本。

## 三层架构设计

一个能复用的分析型 Skill，本质是把"确定性计算"交给脚本、"模糊判断"交给模型、"领域知识"交给参考文件、"格式规范"交给模板。四者各司其职：

| 层 | 文件 | 职责 | 由谁执行 |
|---|---|---|---|
| 控制层 | `SKILL.md` | 工作流编排、假设生成与排序 | Claude |
| 计算层 | `scripts/extract.py` | 正则抽取、时间聚类、统计 | Python（确定性） |
| 知识层 | `references/patterns.md` | 错误码含义、常见根因对照表 | Claude 按需读取 |
| 表现层 | `templates/report.md` | 报告骨架与字段约束 | Claude 填充 |

关键设计原则来自官方最佳实践：**默认假设 Claude 已经很聪明**——它知道什么是堆栈、什么是超时，所以参考文件只放它不可能猜到的内部知识（自家错误码、本集群拓扑约定），不要写它已经懂的常识。

```mermaid
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
```

## 步骤 1：创建骨架与 frontmatter

个人级 Skill 放在 `~/.claude/skills/` 下，对所有项目生效；项目级放在 `.claude/skills/` 下。这里做项目级，方便随仓库分发给团队。

```bash
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 在加载正文前先读取的元数据，控制发现、调用、权限与执行位置：

```yaml
---
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` 写得太泛（如"分析日志"）或漏掉了用户实际会说的触发短语。把它写成包含动词 + 对象 + 场景的完整句。

## 步骤 2：编写 SKILL.md 主控流程（工作流模式）

frontmatter 之后就是正文。正文采用官方推荐的**条件工作流模式（conditional workflow pattern）**——用编号步骤告诉 Claude 在什么条件下做什么，而不是堆细节。

```markdown
# 日志根因分析

你是一位资深 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 ...` 并保证工作目录正确。

## 步骤 3：编写计算层 scripts/extract.py

这是整个 Skill 的"确定性引擎"。它的设计目标只有一个：**把非结构化日志变成结构化、可复核的中间产物**。

```python
#!/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` 调用，不强制可执行，但养成习惯）：

```bash
chmod +x scripts/extract.py
```

**手动验证中间产物**（这是 Skill 开发的核心纪律——先确保脚本能独立跑通）：

```bash
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 与空格分隔两种。针对自家日志格式扩展正则即可，这恰恰是"内部知识"该沉淀进脚本而非交给模型猜的地方。

## 步骤 4：编写知识层 references/patterns.md

参考文件体现**渐进披露**：它平时不进上下文，只有 SKILL.md 步骤 4 明确要求查错误码时才被读取。所以可以放心写得详尽——这正是放领域知识的正确位置。

```markdown
# 错误码与常见根因对照表

> 本文件仅在被需要时读取。新增内部错误码时追加到此。

## 数据库相关
| 现象 | 常见根因 | 首要排查 |
|---|---|---|
| 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 正文，因为它一旦加载就跨多轮常驻。

## 步骤 5：编写表现层 templates/report.md

模板模式（template pattern）让最终产物的格式稳定、可被下游脚本解析。模板里只放**结构与约束**，不放内容。

```markdown
# 根因分析报告

**分析对象**：{{文件名}}　**分析时间**：{{当前会话时间}}　**事件总数**：{{N}}

## 1. 主异常
- 类型：{{最频/最早爆发的异常签名}}
- 爆发窗口：{{起止时间}}，峰值 {{N}} 条/分钟

## 2. 因果候选链
{{按时间排列的关键事件序列，每条带事件编号与时间戳}}

## 3. 根因假设（按证据充分度排序）
### 假设 1：{{一句话根因}} — 置信度 高/中/低
- 依据：事件 #{{n}}, #{{m}}
- 反证（若有）：{{...}}

## 4. 建议排查动作
1. {{可立即执行的第一步}}
2. {{...}}

## 5. 复核清单
- [ ] 所列时间戳均可在原日志中检索到
- [ ] 每条假设标注了依据事件
- [ ] 证据不足处已明确标注
```

> **设计要点**：第 5 节"复核清单"是**反馈循环**的体现——它强迫模型在交付前自检，把"可验证"从中间产物延伸到最终报告。

## 步骤 6：用真实日志跑通端到端

构造一份贴近真实的故障日志（连接池耗尽导致服务雪崩）：

```bash
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`，给出类似"数据库连接超时 → 请求堆积 → 堆内存耗尽雪崩"的因果链，并建议先查数据库连接池与慢查询。

## 步骤 7：调试、触发调优与团队分发

**调试中间产物**：临时把动态注入行改成 `!`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 中跑，避免污染主上下文 |
| 评估方式 | 给定一份隐藏答案的故障日志，以报告是否锁定正确根因 + 复核清单全部可验证为评分标准 |

## 延伸阅读

- **官方 Skills 总览**——`code.claude.com/docs/en/skills`，目录结构、frontmatter 与动态注入的权威来源
- **Skill 编写最佳实践**——`platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices`，本教程四要素（工具脚本、可验证中间输出、渐进披露、模板）的原始出处
- **frontmatter 全字段速查**——15 个字段的默认值与适用场景，含 `context: fork`、`paths` 自动激活、`hooks` 生命周期等高级项
- **bundled skills 源码**——`/code-review`、`/debug`、`/run`、`/verify` 等内置 Skill 是学习"工作流模式"与"反馈循环"的最佳范例，可用 `/run-skill-generator` 录制自家项目的运行配方

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — Skills 目录结构、frontmatter、动态上下文注入（`!`cmd``）与实时变更检测的官方权威说明
2. [Skill authoring best practices — Claude API Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices) — 简洁原则、自由度匹配、工作流/反馈循环模式、可验证中间输出、渐进披露与反模式的官方指南
3. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide) — 15 个 frontmatter 字段（name、description、allowed-tools、disable-model-invocation、context、paths、hooks 等）的默认值与示例速查
4. [Claude Code Skills: How to Set Them Up and Use Them](https://dev.to/padawanabhi/claude-code-skills-how-to-set-them-up-and-use-them-1chm) — SKILL.md 文件夹结构、支持文件（scripts/references/templates）组织与上手示例
5. [The Complete .claude Directory Guide for Claude Code [2026]](https://computingforgeeks.com/claude-code-dot-claude-directory-guide/) — `.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 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
