关于 exploring-apm-traces
exploring-apm-traces 是用于调查 PostHog 所采集 OpenTelemetry 分布式追踪的技能。它帮助工程师评估服务健康状况、定位延迟与错误,并通过专用 APM 工具检查追踪和 Span 数据。
主要功能
- 01基于基线比较服务健康状况
- 02搜索 Span 并获取完整追踪
- 03汇总延迟、错误和操作
- 04检查调用树与 Span 属性
- 05用脚本解析大型追踪结果
适用场景
- 01识别健康状况恶化的服务
- 02追踪慢请求的调用路径
- 03查明追踪中的错误位置
- 04比较异常 Span 的属性
MCP 相关
该技能使用 PostHog MCP 工具查询 APM Span、获取追踪,并汇总服务和 Span 数据。对于 SLO 燃烧率问题,它要求先使用受治理的指标工具,再进行 APM 分析。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ skills --skill exploring-apm-traces安装后在 Claude Code 中输入/exploring-apm-traces确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/spans-and-fields.mdscripts/_common.pyextract_span.pyprint_summary.pyprint_timeline.pysearch_spans.pyshow_structure.py详细介绍
探索 APM 跟踪(OpenTelemetry spans)
PostHog 会从 OpenTelemetry 捕获分布式跟踪。每条跟踪都是一棵由 spans 构成的树,表示请求在各项服务中的路径。
**消歧说明:**此技能适用于 APM / OpenTelemetry 跟踪。请勿与 AI 可观测性跟踪(Agent/模型 $ai_* 事件)或 日志(posthog:query-logs、posthog:logs-*)混淆。
优先使用受治理的指标
如果问题涉及 SLO 消耗率,请先调用 posthog:metric-list,并查找 slo_explicit_burn_by_operation。使用 posthog:data-catalog-metric-run 运行一个已批准且未发生漂移的匹配项,作为规范的核心结论。如果用户还询问哪个操作导致了消耗率,则先回答核心结论,然后使用下面的聚合和跟踪工作流进行非规范的细分。如果没有匹配的受治理指标,请说明这一点,并将派生指标标记为非规范。
可用工具
| 工具 | 用途 |
|---|---|
posthog:apm-service-health | 根据基线查看各服务的状态:从此处开始 |
posthog:query-apm-spans | 搜索和筛选 spans(紧凑列表视图) |
posthog:apm-trace-get | 获取一个十六进制 trace_id 的完整 span 列表 |
posthog:apm-spans-aggregate | 按操作聚合(数量、p50/p95、错误) |
posthog:apm-spans-tree | 按 (parent, child) 边聚合调用树 |
posthog:apm-spans-count | 返回 span 数量的标量值 — 低成本筛选预检 |
posthog:apm-spans-sparkline | 按时间统计 span 数量(补零时间序列) |
posthog:apm-spans-duration-histogram | 按对数刻度时长区间统计跟踪数量 |
posthog:apm-attribute-breakdown | 按一个属性值分组统计 span 数量 |
posthog:apm-services-list | 列出不同的服务名称 |
posthog:apm-attributes-list | 列出 span 或资源属性键 |
posthog:apm-attribute-values-list | 列出指定属性键的值 |
响应架构以及 kind/status_code 枚举,请参阅 references/spans-and-fields.md。
工作流:通过 URL 调试跟踪
步骤 1 — 获取跟踪
posthog:apm-trace-get
{
"trace_id": "<hex_trace_id>"
}
响应格式为 { results: [span, span, …], _posthogUrl: "…" },其中包含该跟踪中所有 span 的扁平列表。
对于扇出请求流,列表可能非常庞大;超出内联限制时,Claude Code 会自动将其持久化到文件中。
结果中包含:
- 每个 span 的
name、service_name、kind、status_code、parent_span_id、duration_nano、is_root_span _posthogUrl— 指向跟踪 UI 中此跟踪的深层链接;请务必在回复中包含此链接,以便用户点击查看
步骤 2 — 使用脚本解析大型结果
如果结果已持久化到文件(跟踪包含数百个跨服务的 spans),请使用解析脚本进行探索。
先查看摘要以了解全貌,然后再深入查看具体内容:
# 1. Overview: services, span count, slowest spans, errors
python3 scripts/print_summary.py /path/to/persisted-file.json
# 2. Indented chronological tree (DFS by parent_span_id)
python3 scripts/print_timeline.py /path/to/persisted-file.json
# 3. Drill into a specific span by name
SPAN="HTTP GET /api/users" python3 scripts/extract_span.py /path/to/persisted-file.json
# 4. Search for a keyword across span names, services, IDs
SEARCH="keyword" python3 scripts/search_spans.py /path/to/persisted-file.json
# 5. When the JSON shape looks unfamiliar
python3 scripts/show_structure.py /path/to/persisted-file.json
所有脚本都支持 MAX_LEN=N 环境变量来控制截断长度(0 = 不限长度)。
树重建(parent_span_id → span_id)
扁平的 span 列表实际上是一棵树。每个 span 都包含:
trace_id— 跟踪中所有 span 的值都相同span_id— 此 span 唯一的十六进制 IDparent_span_id— 指向父级的span_id(根节点使用零填充的十六进制值000…000)is_root_span— 用于标记跟踪入口的便捷标志
重建树的方法:
is_root_span为 true(或parent_span_id == "00000000…")的 span 是根 span。- 其他所有 span 都是其
parent_span_id与之匹配的 span 的子节点。 - 按
parent_span_id分组,从每个根节点开始向下遍历。
scripts/print_timeline.py 会为你完成这些操作,并打印出按 DFS 缩进的树。
调查模式
“我的服务运行状况如何?”(从这里开始)
apm-service-health在一次调用中为每个服务评分:请求数、错误率、p50/p95/p99、与基线窗口相比的变化,以及包含reason的status(silent、regressed、degraded、new、recovered、improved、ok)。结果按问题严重程度从高到低排列。- 使用
compare选择基线:previous_period(默认值)、previous_day、previous_week,或在query旁指定显式的baselineDateRange。使用environment和serviceNames限定范围。 - 运行
next中的调用。这些调用已预先填好参数:使用apm-spans-aggregate查看退化和性能下降服务背后的操作,使用apm-spans-sparkline查看无数据服务从何时停止产生数据,使用query-apm-spans查询错误 span。 - 不要根据
apm-spans-aggregate的行或针对每个服务各调用一次apm-spans-count来汇总服务状况。操作行的百分位数无法相加得到服务级百分位数。
每日服务运行状况报告
- 使用
dateRange: {"date_from": "-24h"}和compare: "previous_day"调用apm-service-health。按照其next调用,查看每个状况不佳服务背后的操作。 - 使用
logs-patterns-diff查找状况不是ok的服务中的新错误模式。 - 使用
logs-alerts-list查看此时间窗口内触发的告警。
“时间都花在哪里了?”
apm-trace-get返回的每个 span 都包含self_time_nano— 即未被子节点覆盖的持续时间。按此字段排序:排在最前的 span 就是实际消耗墙钟时间最多的地方。self_time_nano较大的父级表示存在未插桩的时间间隙(工作发生在该父级内部,而不是任何已记录的子节点中)。- 运行
print_summary.py— 它会列出按duration_nano排序最慢的 5 个 span。 - 对于较难分析的跟踪,运行
print_timeline.py并查看缩进显示的持续时间 — 这样可以看出时间主要消耗在一个子 span 上,还是分散在许多子 span 上。 - 若要深入查看某个慢 span,运行
SPAN="<name>" python3 scripts/extract_span.py FILE。 - 对于聚合层面的“哪个子节点占主导”问题,使用
apm-spans-tree并查看calls_per_parent_invocation— 它能区分每次调用都很慢的子节点和仅仅在每个父节点下运行 20 次的子节点。
“错误发生在哪里?”
print_summary.py会列出所有status_code == 2(Error)的 span。每个条目都显示服务、span 名称和父级上下文。- 沿着错误 span 的
parent_span_id向上遍历树,查看导致错误的请求路径。 - 错误详情位于每个 span 的
attributes映射中(例如exception.message、exception.type),并且会包含在跟踪负载中 — 直接从错误 span 中读取即可。apm-attribute-values-list用于发现多个 span 中的属性值,并非读取单个 span 属性的前置条件。
“请求是否访问了服务 X?”
- 运行
print_summary.py— 它会打印跟踪中涉及的服务集合。 - 如果缺少服务 X,则请求从未到达该服务(也可能是缺少插桩 — 使用
serviceNames: ["X"]调用apm-service-health,查看 X 最近是否产生过 span,以及何时停止)。
“异常 Span 有什么不同?”(过度代表的值)
- 限定异常数据集:使用
filterGroup筛选status_code = Error,或设置duration阈值。 - 使用
apm-attributes-list查找候选键——常见的可疑项:server.address、http.response.status_code、db.system,以及k8s.pod.name/service.version等资源键。 - 针对异常数据集,对每个候选键运行
apm-attribute-breakdown。某个值占据大部分count,就说明它具有标志性。 - 确认是否过度代表:去掉异常数据集筛选后重新运行(或比较每行的
error_count / count)。某个值占错误的 95%,但只占流量的 10%,它就是元凶;如果它在错误和流量中的占比都为 95%,那它只是流量大。
“什么时候开始飙升?”(随时间变化的趋势)
- 使用筛选条件运行
apm-spans-sparkline→ 获取每个时间桶中的总数(补零,每个时间窗口约有 50 个自适应时间桶)。 - 使用相同调用并设置
statusCodes: [2]→ 获取每个时间桶中的错误数。 - 每个时间桶的错误率 = 错误数 / 总数;比率开始上升的时间桶就是飙升开始的时间。
- 缩小范围:使用该时间桶附近的较窄
dateRange重新运行,然后通过query-apm-spans获取原始 Span。
“延迟分布是什么样的?”
apm-spans-duration-histogram→ 获取 ROOT Span 按对数刻度(1-2-5 序列)划分的各延迟时间桶中的跟踪数。- 出现第二个峰值或长尾 = 存在一个独立的慢速数据集;记下其
bucket_ns范围。 - 使用
duration筛选条件(纳秒)和orderBy: "duration",通过query-apm-spans获取实际的慢速跟踪。
“扇出情况是否合理?”
print_timeline.py会显示缩进——树较宽意味着并行调用,树较深意味着存在顺序依赖。- 查找
Client(3)类型的 Span,以及被调用服务上与其匹配的Server(2)类型的 Span——这表示一次同步下游调用。
按属性搜索(例如 http.method=POST)
每个 Span 都包含一个 attributes 映射(Span 级别的 OTel 属性,如 http.method、db.statement),并且位于负载中——所以对于已有的 Span,只需读取它即可。资源属性(k8s 标签、service.version)不在负载中。若要按属性筛选整个数据集:
- 使用
apm-attributes-list/apm-attribute-values-list查找键和值(尤其是资源属性)。 - 使用类型为
span_attribute或span_resource_attribute的filterGroup条目,重新运行query-apm-spans。
构建 UI 链接
apm-trace-get 会返回一个 _posthogUrl 深层链接,用于在跟踪 UI 中打开跟踪——务必向用户展示该链接,以便他们在 PostHog UI 中进行验证。
query-apm-spans 不会返回 _posthogUrl。
要链接到通过查询工具找到的跟踪,请将其 trace_id 传给 apm-trace-get,并展示该响应中的 _posthogUrl。
切勿手动构建 PostHog URL。
查找跟踪
使用 posthog:query-apm-spans 搜索和筛选 Span。请注意,它返回的是 Span,而不是树——传入 query.traceId,或从结果中获取 trace_id 并传给 apm-trace-get,即可获取树。
筛选前先发现
构造筛选条件前,先了解项目中的实际情况:
- 确认服务是否存在——调用
apm-services-list,查看哪些服务发出了 Span。 - 查找可筛选的属性——调用
apm-attributes-list,并设置attribute_type: "span"或"resource"。 - 获取实际值——使用某个键调用
apm-attribute-values-list,查看当前使用的实际值。
然后再构造 query-apm-spans 筛选条件。自定义属性因项目而异,无法猜测。
按筛选条件搜索
posthog:query-apm-spans
{
"query": {
"serviceNames": ["api-gateway"],
"dateRange": {"date_from": "-1h"},
"filterGroup": [
{"key": "http.status_code", "operator": "gt", "type": "span_attribute", "value": "499"}
]
}
}
按跟踪 ID 搜索(已知时)
posthog:apm-trace-get
{
"trace_id": "0123456789abcdef0123456789abcdef"
}
常见注意事项
- 时长单位为纳秒。 1 秒 =
1_000_000_000。query-apm-spans中duration的筛选值也使用纳秒。 status_code == 2表示错误。0表示未设置,1表示正常。在 UI 筛选器中使用OK来匹配{0, 1}。kind是一个 0–5 的整数:0 未指定,1 内部,2 服务端,3 客户端,4 生产者,5 消费者。- 根 span 的
parent_span_id为"0000000000000000"(16 个十六进制零字符,与 8 字节 span ID 的宽度一致——而不是 16 字节 trace ID 的宽度),而非 null。
解析大型 trace 结果
Trace 工具的结果为 JSON。当结果过大,无法直接内联读取时,Claude Code 会将其保存到文件中。
已保存文件格式
[{ "type": "text", "text": "{\"results\": [...], \"_posthogUrl\": \"...\"}" }]
script/ 中的每个脚本都会在解析前解开此封装。
Trace JSON 结构
results (array of span dicts)
└── each span:
├── uuid, trace_id, span_id, parent_span_id (hex strings)
├── name, kind (int 0–5), service_name
├── status_code (int 0–2), is_root_span (bool)
├── timestamp, end_time (ISO 8601)
├── duration_nano (int, nanoseconds)
├── attributes (map of span-level OTel attributes, e.g. db.statement, http.url)
└── matched_filter (0/1 — 1 if this span matched the query-apm-spans filter, 0 if it
only shares a trace with a match; always present, only meaningful from query-apm-spans)
可用脚本
| 脚本 | 用途 | 用法 |
|---|---|---|
print_summary.py | Trace 元数据、服务、最慢的 spans、错误 | python3 scripts/print_summary.py FILE |
print_timeline.py | 根据 parent_span_id 遍历生成 DFS 缩进树 | python3 scripts/print_timeline.py FILE |
extract_span.py | 匹配指定名称的 spans 的完整行及其父项/子项 | SPAN="name" python3 scripts/extract_span.py FILE |
search_spans.py | 在 name、service_name、ID 中查找关键词 | SEARCH="kw" python3 scripts/search_spans.py FILE |
show_structure.py | 显示 JSON 键和类型,不显示值 | python3 scripts/show_structure.py FILE |
提示
- 始终为
query-apm-spans设置dateRange——不指定时间范围的查询速度很慢。默认值为-1h;仅在需要时扩大范围。 - 回复中始终包含
apm-trace-get返回的_posthogUrl,方便用户点击查看 trace。 - span 级属性确实包含在
apm-trace-get/query-apm-spans的载荷中(每个 span 的attributes映射)。资源属性则不包含在内——请使用apm-attributes-list(类型为resource)和apm-attribute-values-list获取。 is_root_span是查找 trace 入口的简单方法——不要通过字符串匹配00000000…。- 对于聚合(按操作统计 p95、查找 span 的最慢子项),请使用
apm-spans-aggregate获取平铺视图,或使用apm-spans-tree获取父→子边——不要转而使用 SQL。