You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取OpenTelemetry指定traceId关联的所有子Span

核心结论

OpenTelemetry 官方核心API本身不提供直接通过traceId查询关联全量子Span的内置方法。所有Span默认在结束上报后就会被SDK释放,不会在进程内存中留存关联关系,需要通过自定义组件实现采集存储,才能满足同Trace下Span查询的需求。


可落地方案

自定义SpanProcessor 进程内捕获同Trace全量Span

这是最适配服务端渲染页面性能条场景的方案,不需要依赖外部存储,直接在服务进程内完成Span收集,延迟最低。

  • 实现逻辑:自定义Span处理器,在Span启动/结束时按traceId分组将Span存入临时内存存储,等当前请求所有逻辑执行完成后,即可读取对应traceId下的全量Span列表
  • 处理器实现代码:
const { SpanProcessor } = require('@opentelemetry/sdk-trace-base');

// 按traceId存储关联Span列表,配套过期机制避免内存泄漏
const traceSpanMap = new Map();
// Trace数据默认过期时间,避免无效数据长期占用内存
const TRACE_DEFAULT_EXPIRE_MS = 10 * 60 * 1000;

class LocalTraceCollectProcessor extends SpanProcessor {
  onStart(span, parentContext) {
    const traceId = span.spanContext().traceId;
    if (!traceSpanMap.has(traceId)) {
      traceSpanMap.set(traceId, {
        spans: [],
        expireAt: Date.now() + TRACE_DEFAULT_EXPIRE_MS
      });
    }
    traceSpanMap.get(traceId).spans.push(span);
  }
  onEnd(span) {
    const traceId = span.spanContext().traceId;
    if (traceSpanMap.has(traceId)) {
      // 每次有Span结束就延后过期时间,保证请求处理完成后数据留存足够读取时间
      traceSpanMap.get(traceId).expireAt = Date.now() + 30 * 1000;
    }
  }
  shutdown() {}
  forceFlush() { return Promise.resolve(); }
}

// 定时清理过期Trace数据
setInterval(() => {
  const now = Date.now();
  for (const [traceId, data] of traceSpanMap.entries()) {
    if (data.expireAt < now) traceSpanMap.delete(traceId);
  }
}, 60 * 1000).unref();

// 对外暴露查询方法
function getSpansByTraceId(traceId) {
  return traceSpanMap.get(traceId)?.spans || [];
}
  • 注册方式:在tracing.js初始化TracerProvider时,将自定义Processor加入处理器列表即可:
const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base');
const provider = new BasicTracerProvider();
// 保留原有上报到Collector的BatchSpanProcessor等配置
provider.addSpanProcessor(new LocalTraceCollectProcessor());

查询后端Trace存储

如果Span已经统一上报到OpenTelemetry Collector、Jaeger、Zipkin等可观测后端,可以直接调用后端提供的Trace查询接口,传入traceId拉取全量Span。但这种方案会产生额外网络请求,不适合当前请求响应周期内拼接性能条的低延迟场景。


相关问题解答
  • 如何遍历Span构成的调用DAG?
    拿到全量Span列表后,根据每个Span的parentSpanId字段构建父子ID映射表,从根Span(parentSpanId为空的入口Span)开始做深度/广度优先遍历,即可还原完整调用链层级。
  • 为什么拿到根Span也无法直接获取子Span?
    OpenTelemetry的Span设计为上下文向下透传模式,子Span创建时只会持有父Span的上下文引用,父Span本身不会维护子Span的引用列表,因此原生不支持向下遍历。
  • 可以只监听指定traceId的Span吗?
    可以在自定义SpanProcessor的onStart逻辑中增加过滤判断,仅将目标traceId对应的Span存入内存,其他Trace的Span直接跳过,可大幅降低内存占用。

Express场景适配示例

在路由逻辑中等待所有业务操作、外部请求执行完成后,即可读取全量Span,过滤出慢调用数据渲染到性能条:

require('./tracing.js');
const opentelemetryApi = require('@opentelemetry/api');
const express = require('express');
const app = express();

app.get('/test', async (req, res) => {
  // 外部HTTP请求会被HTTP插桩自动生成子Span
  await fetch('https://matrix-client.matrix.org/_matrix/client/versions');

  const activeCtx = opentelemetryApi.context.active();
  const span = opentelemetryApi.trace.getSpan(activeCtx);
  const traceId = span.spanContext().traceId;
  // 获取当前Trace下所有Span
  const allSpans = getSpansByTraceId(traceId);
  // 过滤需要展示的慢请求(示例:筛选耗时超过100ms的Span)
  const slowCallSpans = allSpans.filter(s => s.duration > 100);

  res.send(`<section><h1>Performance bar</h1> <p>Trace ID: ${traceId}, 慢请求数: ${slowCallSpans.length}</p></section> other page HTML`);
});

app.listen(3000);

注意:读取Span属性时要等所有子操作执行完成、对应Span触发onEnd回调,否则可能拿不到完整的耗时、状态等最终数据。


内容的提问来源于stack exchange,提问作者MLM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 14:15:42