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

Express链路追踪异常:无法获取Active Span及Jaeger无数据问题

OpenTelemetry集成问题:TypeError与自动Span不生效

问题现象

在JavaScript中集成OpenTelemetry(OTel)时遇到两个核心问题:

  1. 抛出错误:TypeError: Cannot read properties of undefined (reading 'spanContext')
  2. Express Instrumentation未自动创建Span,仅手动创建的Span能在Jaeger中看到追踪数据;仅通过init("items-service", 8081)初始化时,Jaeger无任何数据显示。

相关代码

otel.js

const { PrometheusExporter } = require("@opentelemetry/exporter-prometheus");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http");
const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { ExpressInstrumentation } = require("@opentelemetry/instrumentation-express");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const { Resource } = require("@opentelemetry/resources");
const { MeterProvider } = require("@opentelemetry/sdk-metrics");
const {
  NodeTracerProvider,
  SimpleSpanProcessor,
  ConsoleSpanExporter,
} = require("@opentelemetry/sdk-trace-node");
const { ATTR_SERVICE_NAME } = require("@opentelemetry/semantic-conventions");

const init = function (serviceName, metricPort) {
  // Define metrics
  const metricExporter = new PrometheusExporter({ port: metricPort }, () => {
    console.log(
      `scrape: http://localhost:${metricPort}${PrometheusExporter.DEFAULT_OPTIONS.endpoint}`
    );
  });

  // Create meter provider
  const meterProvider = new MeterProvider({
    readers: [metricExporter],
  });

  // Get meter
  const meter = meterProvider.getMeter(serviceName);

  // Define traces
  // const traceExporter = new ConsoleSpanExporter();
  const traceExporter = new OTLPTraceExporter();

  const processor = new SimpleSpanProcessor(traceExporter);

  // Create trace provider
  const provider = new NodeTracerProvider({
    resource: new Resource({
      [ATTR_SERVICE_NAME]: serviceName,
    }),
    spanProcessors: [processor],
  });

  provider.register();

  // Register instrumentations
  registerInstrumentations({
    instrumentations: [new ExpressInstrumentation(), new HttpInstrumentation()],
  });

  // Get tracer
  const tracer = provider.getTracer(serviceName);

  // Return meter & tracer
  return { meter, tracer };
};

module.exports = init;

items-service.js

const axios = require("axios");
const express = require("express");
const bodyParser = require("body-parser");
const init = require("./otel.js");
const api = require("@opentelemetry/api");

init("items-service", 8081);
const app = express();

// Define prometheus counter
const httpCounter = meter.createCounter("http_calls");
app.use((req, res, next) => {
  httpCounter.add(1);
  next();
});

app.get("/data", async (req, res) => {
  try {
    if (req.query["fail"]) {
      throw new Error("A really bad error :/");
    }
    const apiResponse = await axios.get("http://localhost:8090/user");
    res.json(apiResponse.data);
  } catch (error) {
    // Get active span
    const activeSpan = api.trace.getSpan(api.context.active());

    if (activeSpan) {
      const traceId = activeSpan.spanContext().traceId;
      // Combine log  with trace
      console.error(`Critical Error, traceId: ${traceId}`);
      activeSpan.recordException(error);
    } else {
      console.warn("No active span found");
    }

    res.sendStatus(500);
  }
});

const server = app.listen(8080, () => {
  console.log("Items service is listening at port 8080");
});

尝试的手动Span代码

const { tracer } = init("items-service", 8081);
...

// Create a span
const span = tracer.startSpan("items-service");
span.addEvent("An error has occurred");
span.end();
console.error(`Critical Error, traceId: ${span.spanContext().traceId}`);

docker-compose.yml

services:
  jaeger:
    image: jaegertracing/all-in-one
    ports:
      - 16686:16686
      - 4317:4317
      - 4318:4318
  prometheus:
    image: prom/prometheus
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - 9090:9090

问题分析

  • 初始化变量未接收:init函数返回了meter和tracer,但原代码中调用init("items-service", 8081)时未接收返回值,导致meter变量未定义,不仅会引发计数器创建失败,还会影响OpenTelemetry上下文的正确绑定。
  • Span处理器配置错误:NodeTracerProvider的构造函数并不支持spanProcessors参数,原代码中直接传入该参数会导致处理器未被正确注册,自动创建的Span无法被导出到Jaeger。
  • OTLP Exporter配置缺失:默认OTLPTraceExporter可能因未显式指定endpoint导致数据无法正确发送到Jaeger(尽管默认端口匹配,但显式配置更可靠)。

解决方案

  • 修正items-service.js的初始化:接收init返回的meter和tracer,确保变量可用:
    const { meter, tracer } = init("items-service", 8081);
    
  • 修正otel.js中的Span处理器注册方式:将构造函数内的spanProcessors参数替换为addSpanProcessor方法:
    // Create trace provider
    const provider = new NodeTracerProvider({
      resource: new Resource({
        [ATTR_SERVICE_NAME]: serviceName,
      }),
    });
    // 新增这行,替换原构造函数内的spanProcessors参数
    provider.addSpanProcessor(processor);
    provider.register();
    
  • 显式配置OTLPTraceExporter的Endpoint:确保追踪数据正确发送到Jaeger的OTLP HTTP端口:
    const traceExporter = new OTLPTraceExporter({
      url: "http://localhost:4318/v1/traces"
    });
    
  • 增强错误处理中的空值判断(可选):避免因activeSpan为undefined引发的报错:
    const activeSpan = api.trace.getSpan(api.context.active());
    if (activeSpan?.spanContext) { // 使用可选链操作符
      const traceId = activeSpan.spanContext().traceId;
      console.error(`Critical Error, traceId: ${traceId}`);
      activeSpan.recordException(error);
    } else {
      console.warn("No active span found");
    }
    

验证

完成以上修改后:

  • 重启服务,调用/data?fail=1接口,不会再抛出spanContext相关错误
  • 访问Jaeger UI,可以看到Express自动创建的请求Span以及错误记录
  • Prometheus计数器也能正常工作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:04:58