关于 root-cause-analysis
root-cause-analysis 是面向 Kopai 的技能,指导使用 OpenTelemetry 链路、日志和指标排查生产问题。它通过 TypeScript SDK 查询定位错误、关联证据、量化影响并整理可能原因。
主要功能
- 01使用类型安全的 TypeScript SDK 查询
- 02分析链路、日志和指标
- 03按错误率与吞吐量排序
- 04通过时间序列分析影响
- 05关联链路与日志
- 06说明 SQLite 查询限制
适用场景
- 01事件分诊时对服务排序
- 02沿链路追查相关日志中的错误
- 03衡量问题开始时间与影响范围
- 04调查延迟和指标变化
MCP 相关
这是使用 Kopai SDK 与 CLI 的技能,并非 MCP 服务或客户端。README 未说明 MCP 集成。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kopai-app/ kopai-mono --skill root-cause-analysis安装后在 Claude Code 中输入/root-cause-analysis确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonreferences/log-filters.mdmetric-filters.mdtrace-filters.mdrules/_sections.mdpattern-distributed.mdpattern-http-errors.mdpattern-log-driven.mdpattern-slow-requests.mdworkflow-check-metrics.mdworkflow-correlate-logs.mdworkflow-find-errors.mdworkflow-get-context.mdworkflow-identify-cause.md详细介绍
使用 Kopai 进行根因分析
根据遥测数据(追踪、日志、指标)排查生产环境问题。Kopai 的分析能力——错误率、吞吐量、延迟、分组、时间序列分桶——位于 SDK 查询 API 中,CLI 并未提供这些能力。因此,每次排查都应先编写一段简短的 TypeScript 脚本;CLI 则保留为快速查询的备用方式。
前提条件
- 服务正在向 Kopai 发送 OpenTelemetry 数据(参见
otel-instrumentation技能)。 - 项目中已安装
@kopai/sdk:npm i @kopai/sdk(其中包含查询 API 和.kopairc读取器)。
代码模式(推荐)
连接 — clientFromConfig() 的读取 .kopairc 的方式与 CLI 完全一致:
import { clientFromConfig } from "@kopai/sdk/node";
// Reads ./.kopairc then ~/.kopairc; defaults to http://localhost:8000.
// Override any field: clientFromConfig({ url, token, configPath, timeout }).
const client = clientFromConfig();
运行 — npx tsx rca.mts。使用 .mts 扩展名:它始终是 ES 模块,因此顶层 await 无需依赖宿主项目的 package.json "type" 即可正常工作。
(如果没有安装 tsx:node --experimental-strip-types rca.mts。)
使用 kq 构建查询,并通过 client.query(q) 运行。 client.query(q) 是运行任何已构建查询的唯一简洁方式,并且具有完整的类型定义——行数据支持自动补全,拼写错误会导致编译错误。聚合查询返回 { data };原始查询返回 { data, nextCursor }。结果行具有完整的类型定义——直接遍历 data,绝不要将其转换为 any/any[]。 聚合度量值的类型为 number(因此 row.error_rate * 100 可直接使用——无需 String(...)/类型转换),分组维度也包含在内(row["service.name"]),而 .timeSeries() 的行还会添加 bucket_start:
const { data } = await client.query(q); // typed rows — do NOT write `data as any[]`
for (const row of data) {
console.log(
row["service.name"],
`${(row.error_rate * 100).toFixed(1)}%`,
row.rps
);
}
输出 — stdout 就是你的结果。以 console.log(JSON.stringify(data, null, 2)) 结束,并将 .build() 和查询调用包在 try/catch 中:
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
try {
const q = kq.traces
.aggregate() /* … */
.build();
const { data } = await client.query(q);
console.log(JSON.stringify(data, null, 2));
} catch (e) {
if (e instanceof KopaiQueryBuildError)
console.error(e.issues); // {path,message}[]
else throw e;
}
后端注意事项与易踩坑点(查询前请阅读)
- 每次查询都需要指定时间范围——
.timeRelative("1h")或.timeAbsolute(startISO, endISO)。回溯范围/粒度必须匹配^[1-9]\d*[smhdw]$("30s"、"15m"、"2h"、"7d")。 StatusCode的取值严格为"Unset" | "Ok" | "Error"(首字母大写)。成功的 span 通常是"Unset",而不是"Ok"。此枚举会进行类型检查:像"ERROR"这样的错误值会导致编译错误,因此大小写问题会自动被发现。无论如何,优先使用errorRate度量(在服务端统计错误数量),而不是筛选原始值。- 请使用
service.name(带点),绝不要使用ServiceName。ServiceName不是可查询列——按它筛选会静默匹配不到任何内容。(在结果行中,值会按你分组时使用的键返回,例如row["service.name"]。) - 繁忙系统中出现空结果,通常意味着列名/取值有误,而非确实没有数据。 在得出“没有错误/没有数据”的结论前,请重新检查列名和取值的大小写——静默返回空结果是得出“系统一切正常”这一错误结论的最常见原因。
- 百分位数(
p50–p999)仅适用于 ClickHouse。 在 SQLite 上会在查询时失败(KopaiError: Percentile measures … not yet supported on the sqlite backend)。延迟分析请先使用Duration的avg/max;将百分位数视为 ClickHouse 升级选项,并放在 try/catch 中处理。 Duration筛选器接受带单位s/m/h/d/w的时长字符串——f.gt("Duration", "1s")、f.lte("Duration", "2h")(也支持gte/lt)。不支持亚秒级单位;亚秒级阈值请传入纳秒数值。(结果行中的Duration仍以纳秒为单位——Duration的avg/max也会以 ns 返回:1ms = 1e6,1s = 1e9。)- 指标查询会预先选择类型:
kq.metrics("Gauge")…、kq.metrics("Sum")…(类型 ∈"Gauge" | "Sum" | "Histogram" | "ExponentialHistogram" | "Summary")。构建器参数会自动固定MetricType,因此无需手动调用.where(f => f.eq("MetricType", …))。值列按类型确定:Gauge/Sum →"Value";Histogram/ExponentialHistogram →"Count" | "Sum" | "Min" | "Max";Summary →"Count" | "Sum"。 searchTraces/searchLogs/searchMetrics是异步可迭代对象(会自动分页)——请使用for await (const row of client.searchLogs({ … })) { … }消费,不要将它们await为数组。若只需单页,请使用searchTracesPage/searchLogsPage/searchMetricsPage→{ data, nextCursor }。限制:kq的.limit()上限为 10000;search*筛选器上限为 1000。
RCA 工作流
-
找出故障服务 — 按错误率和吞吐量对服务排序。
errorRate会在服务端处理StatusCode,因此无需猜测其值:const q = kq.traces .aggregate() .measure((m) => m.errorRate("error_rate")) .measure((m) => m.throughput("rps")) .measure((m) => m.count("spans")) .dimension("service.name") .timeRelative("1h") .summary() .orderByMeasure("error_rate", "desc") .build(); const { data } = await client.query(q);如需优先从日志入手进行排查,请提取错误级别日志,即
SeverityNumber >= 17(无论文本大小写如何,都能匹配 ERROR/FATAL):client.query(kq.logs.raw().where(f => f.gte("SeverityNumber", 17)).timeRelative("1h").limit(50).build())。 请参阅workflow-find-errors。 -
获取完整的 Trace 上下文 —
const spans = await client.getTrace(traceId)。检查Duration(瓶颈)、ParentSpanId(调用链)、StatusMessage、SpanKind。 请参阅workflow-get-context。 -
将日志与 Trace 关联起来 —
client.query(kq.logs.raw().where(f => f.eq("TraceId", traceId)).timeRelative("1h").build())(或遍历client.searchLogs({ traceId }))。查找最早的错误及其堆栈跟踪。 请参阅workflow-correlate-logs。 -
量化影响 / 定位开始时间 — 这是 CLI 无法完成的操作。按时间对指标分桶,查看问题何时开始,以及影响范围有多大。注意数据保留期限的下限:如果最早的分桶已经升高,真实的开始时间就在该时间点或更早——应如实说明,而不要把这个下限称为“突变”:
const q = kq.traces .aggregate() .measure((m) => m.errorRate("error_rate")) .measure((m) => m.avg("Duration", "avg_ns")) .measure((m) => m.max("Duration", "max_ns")) .dimension("service.name") .timeRelative("3h") .timeSeries("5m") .orderByMeasure("error_rate", "desc") .build(); const { data } = await client.query(q);当信号是指标而非 Trace 时,请预先选择类型,让系统自动固定
MetricType——例如,按时间对 Gauge 分桶:const q = kq .metrics("Gauge") .aggregate() .measure((m) => m.avg("Value", "avg_value")) .where((f) => f.eq("MetricName", "process.cpu.utilization")) .timeRelative("3h") .timeSeries("5m") .build(); const { data } = await client.query(q);请参阅
workflow-check-metrics。 -
呈现调查结果 — 提供有证据支持的根因(具体的 TraceId、日志条目、指标变化)、影响范围以及建议的修复方案。可以提出创建事件仪表板(请参阅
create-dashboard技能)。请参阅workflow-identify-cause。
快速示例
// rca-triage.mts — run: npx tsx rca-triage.mts
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
import { clientFromConfig } from "@kopai/sdk/node";
const client = clientFromConfig();
try {
const q = kq.traces
.aggregate()
.measure((m) => m.errorRate("error_rate"))
.measure((m) => m.throughput("rps"))
.measure((m) => m.avg("Duration", "avg_ns"))
.measure((m) => m.max("Duration", "max_ns"))
.measure((m) => m.count("spans"))
.dimension("service.name")
.timeRelative("1h")
.summary()
.orderByMeasure("error_rate", "desc")
.build();
const { data } = await client.query(q); // typed rows
console.log(JSON.stringify(data, null, 2));
} catch (e) {
if (e instanceof KopaiQueryBuildError) console.error(e.issues);
else throw e;
}
规则
1. 工作流(关键)
workflow-find-errors- 查找错误 Trace 和错误级别日志workflow-get-context- 获取完整的 Trace 上下文workflow-correlate-logs- 将日志与 Trace 关联起来workflow-check-metrics- 使用聚合 / 时间序列查询量化影响workflow-identify-cause- 确定根因并呈现调查结果
2. 模式(高优先级)
pattern-http-errors- HTTP 错误调试pattern-slow-requests- 慢请求分析pattern-distributed- 分布式故障跟踪pattern-log-driven- 日志驱动的调查
阅读 rules/<rule-name>.md 了解详情。
提示
- 从聚合数据入手(按服务查看错误率 / 延迟),然后深入查看一条 Trace。
- 优先使用
errorRate度量,而不是筛选StatusCode(在服务端计数)。 - 使用
client.query(q)执行任何构建好的查询 — 查询具有完整类型支持(行可自动补全,拼写错误会导致编译错误)。 - 使用字符串筛选
Duration(f.gt("Duration", "1s"));结果行中的Duration仍以纳秒为单位。Trace 调用链中最早的错误通常最接近根因。 - 使用
.timeSeries(granularity)查找回归问题的 开始时间 — 不要将数据保留期限的下限误认为开始时间。 - 对于指标,请预先选择类型 —
kq.metrics("Gauge")…(自动固定MetricType,并为值列提供类型)。 - 在 SQLite 上,延迟 =
Duration的avg/max;在 ClickHouse 上改用p95/p99。
CLI 回退方案(快速单次查询)
CLI 适用于单次查询,但没有查询 / 聚合命令。注意,StatusCode 的值是字面量 "Error"。
npx @kopai/cli traces search --status-code Error --limit 20 --json
npx @kopai/cli traces get <traceId> --json
npx @kopai/cli logs search --trace-id <traceId> --severity-min 17 --json
npx @kopai/cli metrics discover --json
参考资料
- trace-filters - Trace 列、筛选 / 度量操作、容器
- log-filters - 日志列、严重性模型、筛选操作
- metric-filters - 指标列、MetricType 固定、聚合