Express链路追踪异常:无法获取Active Span及Jaeger无数据问题
OpenTelemetry集成问题:
TypeError与自动Span不生效 问题现象
在JavaScript中集成OpenTelemetry(OTel)时遇到两个核心问题:
- 抛出错误:
TypeError: Cannot read properties of undefined (reading 'spanContext') - 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
相关产品推荐
相关产品推荐

