mcpField
E
exploring-apm-traces

exploring-apm-traces

github.com/posthog云与 DevOps76361 次安装今天更新
代码仓库

关于 exploring-apm-traces

exploring-apm-traces 是用于调查 PostHog 所采集 OpenTelemetry 分布式追踪的技能。它帮助工程师评估服务健康状况、定位延迟与错误,并通过专用 APM 工具检查追踪和 Span 数据。

主要功能

  1. 01基于基线比较服务健康状况
  2. 02搜索 Span 并获取完整追踪
  3. 03汇总延迟、错误和操作
  4. 04检查调用树与 Span 属性
  5. 05用脚本解析大型追踪结果

适用场景

  1. 01识别健康状况恶化的服务
  2. 02追踪慢请求的调用路径
  3. 03查明追踪中的错误位置
  4. 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.md
scripts/
_common.py
extract_span.py
print_summary.py
print_timeline.py
search_spans.py
show_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 唯一的十六进制 ID
  • parent_span_id — 指向父级的 span_id(根节点使用零填充的十六进制值 000…000)
  • is_root_span — 用于标记跟踪入口的便捷标志

重建树的方法:

  1. is_root_span 为 true(或 parent_span_id == "00000000…")的 span 是根 span。
  2. 其他所有 span 都是其 parent_span_id 与之匹配的 span 的子节点。
  3. 按 parent_span_id 分组,从每个根节点开始向下遍历。

scripts/print_timeline.py 会为你完成这些操作,并打印出按 DFS 缩进的树。

调查模式

“我的服务运行状况如何?”(从这里开始)
  1. apm-service-health 在一次调用中为每个服务评分:请求数、错误率、p50/p95/p99、与基线窗口相比的变化,以及包含 reason 的 status(silent、regressed、degraded、new、recovered、improved、ok)。结果按问题严重程度从高到低排列。
  2. 使用 compare 选择基线:previous_period(默认值)、previous_day、previous_week,或在 query 旁指定显式的 baselineDateRange。使用 environment 和 serviceNames 限定范围。
  3. 运行 next 中的调用。这些调用已预先填好参数:使用 apm-spans-aggregate 查看退化和性能下降服务背后的操作,使用 apm-spans-sparkline 查看无数据服务从何时停止产生数据,使用 query-apm-spans 查询错误 span。
  4. 不要根据 apm-spans-aggregate 的行或针对每个服务各调用一次 apm-spans-count 来汇总服务状况。操作行的百分位数无法相加得到服务级百分位数。
每日服务运行状况报告
  1. 使用 dateRange: {"date_from": "-24h"} 和 compare: "previous_day" 调用 apm-service-health。按照其 next 调用,查看每个状况不佳服务背后的操作。
  2. 使用 logs-patterns-diff 查找状况不是 ok 的服务中的新错误模式。
  3. 使用 logs-alerts-list 查看此时间窗口内触发的告警。
“时间都花在哪里了?”
  1. apm-trace-get 返回的每个 span 都包含 self_time_nano — 即未被子节点覆盖的持续时间。按此字段排序:排在最前的 span 就是实际消耗墙钟时间最多的地方。self_time_nano 较大的父级表示存在未插桩的时间间隙(工作发生在该父级内部,而不是任何已记录的子节点中)。
  2. 运行 print_summary.py — 它会列出按 duration_nano 排序最慢的 5 个 span。
  3. 对于较难分析的跟踪,运行 print_timeline.py 并查看缩进显示的持续时间 — 这样可以看出时间主要消耗在一个子 span 上,还是分散在许多子 span 上。
  4. 若要深入查看某个慢 span,运行 SPAN="<name>" python3 scripts/extract_span.py FILE。
  5. 对于聚合层面的“哪个子节点占主导”问题,使用 apm-spans-tree 并查看 calls_per_parent_invocation — 它能区分每次调用都很慢的子节点和仅仅在每个父节点下运行 20 次的子节点。
“错误发生在哪里?”
  1. print_summary.py 会列出所有 status_code == 2(Error)的 span。每个条目都显示服务、span 名称和父级上下文。
  2. 沿着错误 span 的 parent_span_id 向上遍历树,查看导致错误的请求路径。
  3. 错误详情位于每个 span 的 attributes 映射中(例如 exception.message、exception.type),并且会包含在跟踪负载中 — 直接从错误 span 中读取即可。apm-attribute-values-list 用于发现多个 span 中的属性值,并非读取单个 span 属性的前置条件。
“请求是否访问了服务 X?”
  1. 运行 print_summary.py — 它会打印跟踪中涉及的服务集合。
  2. 如果缺少服务 X,则请求从未到达该服务(也可能是缺少插桩 — 使用 serviceNames: ["X"] 调用 apm-service-health,查看 X 最近是否产生过 span,以及何时停止)。
“异常 Span 有什么不同?”(过度代表的值)
  1. 限定异常数据集:使用 filterGroup 筛选 status_code = Error,或设置 duration 阈值。
  2. 使用 apm-attributes-list 查找候选键——常见的可疑项:server.address、http.response.status_code、db.system,以及 k8s.pod.name / service.version 等资源键。
  3. 针对异常数据集,对每个候选键运行 apm-attribute-breakdown。某个值占据大部分 count,就说明它具有标志性。
  4. 确认是否过度代表:去掉异常数据集筛选后重新运行(或比较每行的 error_count / count)。某个值占错误的 95%,但只占流量的 10%,它就是元凶;如果它在错误和流量中的占比都为 95%,那它只是流量大。
“什么时候开始飙升?”(随时间变化的趋势)
  1. 使用筛选条件运行 apm-spans-sparkline → 获取每个时间桶中的总数(补零,每个时间窗口约有 50 个自适应时间桶)。
  2. 使用相同调用并设置 statusCodes: [2] → 获取每个时间桶中的错误数。
  3. 每个时间桶的错误率 = 错误数 / 总数;比率开始上升的时间桶就是飙升开始的时间。
  4. 缩小范围:使用该时间桶附近的较窄 dateRange 重新运行,然后通过 query-apm-spans 获取原始 Span。
“延迟分布是什么样的?”
  1. apm-spans-duration-histogram → 获取 ROOT Span 按对数刻度(1-2-5 序列)划分的各延迟时间桶中的跟踪数。
  2. 出现第二个峰值或长尾 = 存在一个独立的慢速数据集;记下其 bucket_ns 范围。
  3. 使用 duration 筛选条件(纳秒)和 orderBy: "duration",通过 query-apm-spans 获取实际的慢速跟踪。
“扇出情况是否合理?”
  1. print_timeline.py 会显示缩进——树较宽意味着并行调用,树较深意味着存在顺序依赖。
  2. 查找 Client(3)类型的 Span,以及被调用服务上与其匹配的 Server(2)类型的 Span——这表示一次同步下游调用。
按属性搜索(例如 http.method=POST)

每个 Span 都包含一个 attributes 映射(Span 级别的 OTel 属性,如 http.method、db.statement),并且位于负载中——所以对于已有的 Span,只需读取它即可。资源属性(k8s 标签、service.version)不在负载中。若要按属性筛选整个数据集:

  1. 使用 apm-attributes-list / apm-attribute-values-list 查找键和值(尤其是资源属性)。
  2. 使用类型为 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,即可获取树。

筛选前先发现

构造筛选条件前,先了解项目中的实际情况:

  1. 确认服务是否存在——调用 apm-services-list,查看哪些服务发出了 Span。
  2. 查找可筛选的属性——调用 apm-attributes-list,并设置 attribute_type: "span" 或 "resource"。
  3. 获取实际值——使用某个键调用 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.pyTrace 元数据、服务、最慢的 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。

评论

支持 Markdown