JavaScript/Node.js OpenTelemetry 接入指南
本文档提供 JavaScript/Node.js 应用接入 OpenTelemetry 的完整指南,包括零代码接入和 SDK 手动接入两种方式,支持 Node.js 和浏览器环境。
目录
概述
JavaScript/Node.js 特性
JavaScript/Node.js 的 OpenTelemetry 支持具有以下特点:
- 成熟稳定: API 和 SDK 已达到 1.0 稳定版本
- 零代码接入: 通过自动埋点库实现零侵入
- 全栈支持: 同时支持 Node.js 服务端和浏览器端
- 丰富的埋点库: 支持 30+ 主流框架和库
- TypeScript 支持: 完整的 TypeScript 类型定义
- 异步友好: 完美支持 Promise 和 async/await
版本要求
- Node.js 版本: 14.0.0+ (推荐 18.0.0+)
- OpenTelemetry JS: v1.0.0+
- 支持架构: x64, arm64
支持的信号
- ✅ Traces: 完全支持
- ✅ Metrics: 完全支持
- ✅ Logs: 支持(通过日志库集成)
适用场景
- Node.js Web 服务(Express、Koa、Fastify)
- Next.js、Nuxt.js 等全栈框架
- 浏览器端应用
- 微服务架构
- Serverless 函数(AWS Lambda、Vercel Functions)
零代码接入(推荐)
零代码接入无需修改应用代码,通过自动埋点库即可实现自动监控。这是最简单快捷的接入方式。
快速开始
1. 安装依赖
只需要安装两个包:
npm install --save @opentelemetry/apinpm install --save @opentelemetry/auto-instrumentations-node
或使用 yarn:
yarn add @opentelemetry/api @opentelemetry/auto-instrumentations-node
或使用 pnpm:
pnpm add @opentelemetry/api @opentelemetry/auto-instrumentations-node
2. 配置环境变量
# 服务信息export OTEL_SERVICE_NAME=my-node-serviceexport OTEL_SERVICE_VERSION=1.0.0export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production# OTLP Exporter(内部平台)export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317export OTEL_EXPORTER_OTLP_PROTOCOL=grpcexport OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317# Traces 配置export OTEL_TRACES_EXPORTER=otlpexport OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1# Metrics 配置export OTEL_METRICS_EXPORTER=otlpexport OTEL_METRIC_EXPORT_INTERVAL=60000
3. 启动应用
使用 --require 参数直接加载自动埋点库,无需创建任何文件:
node --require @opentelemetry/auto-instrumentations-node/register app.js
就这么简单!您的应用现在已经自动埋点并开始发送遥测数据了。
其他启动方式
方式 1: 使用 NODE_OPTIONS 环境变量
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"node app.js
方式 2: 在 package.json 中配置
{"scripts": {"start": "node --require @opentelemetry/auto-instrumentations-node/register app.js","dev": "node --require @opentelemetry/auto-instrumentations-node/register --watch app.js"}}
方式 3: 使用 env 命令(一行命令)
env OTEL_SERVICE_NAME=my-service \OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317 \OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317 \node --require @opentelemetry/auto-instrumentations-node/register app.js
资源检测器配置
默认情况下,所有 SDK 资源检测器都会被使用。您可以通过环境变量控制:
# 启用特定的资源检测器export OTEL_NODE_RESOURCE_DETECTORS=env,host,os# 禁用所有资源检测器export OTEL_NODE_RESOURCE_DETECTORS=""
可用的资源检测器:
env: 从环境变量读取host: 主机信息os: 操作系统信息process: 进程信息service: 服务信息
支持的自动埋点库
@opentelemetry/auto-instrumentations-node 包含以下自动埋点库:
- HTTP/HTTPS: 标准库 http/https 模块
- Express: Express.js Web 框架
- Koa: Koa.js Web 框架
- Fastify: Fastify Web 框架
- NestJS: NestJS 框架
- Hapi: Hapi.js 框架
- Restify: Restify 框架
- gRPC: gRPC 客户端和服务端
- MongoDB: MongoDB 驱动
- Redis: ioredis 和 node_redis
- MySQL: mysql2 和 mysql
- PostgreSQL: pg
- Elasticsearch: @elastic/elasticsearch
- AWS SDK: AWS SDK v2 和 v3
- GraphQL: graphql
- Winston: Winston 日志库
- Pino: Pino 日志库
- Bunyan: Bunyan 日志库
- DNS: dns 模块
- Net: net 模块
选择性启用埋点库
如果不想启用所有埋点库,可以手动选择:
const { NodeSDK } = require('@opentelemetry/sdk-node');const {HttpInstrumentation,ExpressInstrumentation,FsInstrumentation,} = require('@opentelemetry/instrumentation');const sdk = new NodeSDK({instrumentations: [new HttpInstrumentation(),new ExpressInstrumentation(),new FsInstrumentation(),],});sdk.start();
验证零代码接入
启动应用后,访问您的服务端点,然后在 APM 平台查看数据:
- Traces: 查看调用链路
- Metrics: 查看请求量、延迟等指标
- Service Map: 查看服务拓扑图
快速验证
# 启动应用node --require @opentelemetry/auto-instrumentations-node/register app.js# 在另一个终端发送测试请求curl http://localhost:3000/health# 等待几秒后,在 APM 平台查看数据
日志级别配置
可以通过环境变量设置日志级别来调试问题:
# 设置日志级别(从低到高)export OTEL_LOG_LEVEL=none # 不输出日志export OTEL_LOG_LEVEL=error # 仅错误export OTEL_LOG_LEVEL=warn # 警告及以上export OTEL_LOG_LEVEL=info # 信息及以上(默认)export OTEL_LOG_LEVEL=debug # 调试信息export OTEL_LOG_LEVEL=verbose # 详细信息export OTEL_LOG_LEVEL=all # 所有日志
注意:
- 生产环境建议使用
info级别 - 日志始终输出到
console,不受环境或调试级别影响 - 调试日志非常详细,可能影响应用性能,仅在需要时启用
SDK 手动接入
SDK 手动接入提供更精细的控制和自定义能力。
安装依赖
npm install --save @opentelemetry/apinpm install --save @opentelemetry/sdk-nodenpm install --save @opentelemetry/exporter-trace-otlp-grpcnpm install --save @opentelemetry/exporter-metrics-otlp-grpcnpm install --save @opentelemetry/resourcesnpm install --save @opentelemetry/semantic-conventions
基础初始化
创建 OpenTelemetry 初始化文件
// otel.jsconst { NodeSDK } = require('@opentelemetry/sdk-node');const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');const { Resource } = require('@opentelemetry/resources');const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');function initOpenTelemetry() {const resource = new Resource({[SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'my-service',[SemanticResourceAttributes.SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || '1.0.0',});const traceExporter = new OTLPTraceExporter({url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',headers: process.env.OTEL_EXPORTER_OTLP_HEADERS ?Object.fromEntries(process.env.OTEL_EXPORTER_OTLP_HEADERS.split(',').map(h => h.split('='))) : {},});const metricExporter = new OTLPMetricExporter({url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',headers: process.env.OTEL_EXPORTER_OTLP_HEADERS ?Object.fromEntries(process.env.OTEL_EXPORTER_OTLP_HEADERS.split(',').map(h => h.split('='))) : {},});const sdk = new NodeSDK({resource,traceExporter,spanProcessor: new BatchSpanProcessor(traceExporter, {maxExportBatchSize: 512,scheduledDelayMillis: 5000,}),metricReader: new PeriodicExportingMetricReader({exporter: metricExporter,exportIntervalMillis: 60000,}),});sdk.start();return {shutdown: async () => {await sdk.shutdown();},};}module.exports = { initOpenTelemetry };
在应用中使用
// app.jsconst { initOpenTelemetry } = require('./otel');// 在应用启动前初始化const otel = initOpenTelemetry();// 应用逻辑const express = require('express');const app = express();app.get('/health', (req, res) => {res.json({ status: 'ok' });});app.listen(3000, () => {console.log('Server running on port 3000');});// 优雅关闭process.on('SIGTERM', async () => {await otel.shutdown();process.exit(0);});
Traces - 手动埋点
创建和使用 Span
const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');async function processOrder(orderId) {const span = tracer.startSpan('process_order', {attributes: {'order.id': orderId,'operation': 'process',},});try {span.addEvent('order_validation_started');await validateOrder(orderId);span.addEvent('order_validated');await chargePayment(orderId);span.addEvent('payment_processed');span.setStatus({ code: 1 }); // OKspan.setAttribute('order.success', true);} catch (error) {span.recordException(error);span.setStatus({ code: 2, message: error.message }); // ERRORthrow error;} finally {span.end();}}
Context 传播
const { context, propagation } = require('@opentelemetry/api');const http = require('http');// HTTP 客户端传播async function makeHttpRequest(url) {const span = tracer.startSpan('http_request', {attributes: {'http.url': url,'http.method': 'GET',},});const activeContext = trace.setSpan(context.active(), span);return new Promise((resolve, reject) => {const options = {url,headers: {},};// 注入 trace context 到 HTTP headerspropagation.inject(activeContext, options.headers);http.get(options, (res) => {span.setAttribute('http.status_code', res.statusCode);span.end();resolve(res);}).on('error', (err) => {span.recordException(err);span.setStatus({ code: 2, message: err.message });span.end();reject(err);});});}// HTTP 服务端提取const express = require('express');const app = express();app.use((req, res, next) => {const activeContext = propagation.extract(context.active(), req.headers);const span = tracer.startSpan('http_request', {kind: 1, // SERVERattributes: {'http.method': req.method,'http.url': req.url,},}, activeContext);context.with(trace.setSpan(activeContext, span), () => {next();});res.on('finish', () => {span.setAttribute('http.status_code', res.statusCode);span.end();});});
Metrics - 指标采集
Counter(计数器)
const { metrics } = require('@opentelemetry/api');const meter = metrics.getMeter('my-service');const requestCounter = meter.createCounter('http.server.requests', {description: 'Total number of HTTP requests',});function handleRequest(method, route, statusCode) {requestCounter.add(1, {'http.method': method,'http.route': route,'http.status_code': statusCode,});}
Histogram(直方图)
const requestDuration = meter.createHistogram('http.server.duration', {description: 'HTTP request duration',unit: 'ms',});function recordDuration(duration, method, route) {requestDuration.record(duration, {'http.method': method,'http.route': route,});}
UpDownCounter(升降计数器)
const activeConnections = meter.createUpDownCounter('http.server.active_connections', {description: 'Number of active HTTP connections',});function connectionOpened() {activeConnections.add(1);}function connectionClosed() {activeConnections.add(-1);}
Observable Gauge(可观察仪表)
const memoryUsage = meter.createObservableGauge('runtime.memory.heap_used', {description: 'Heap memory used',unit: 'bytes',});// 注册回调函数meter.addBatchObservableCallback((observableResult) => {const memUsage = process.memoryUsage();observableResult.observe(memoryUsage, {'memory.type': 'heap',}, memUsage.heapUsed);}, [memoryUsage]);
Logs - 日志集成
集成 Winston
const winston = require('winston');const { trace } = require('@opentelemetry/api');// 创建 loggerconst logger = winston.createLogger({format: winston.format.combine(winston.format.timestamp(),winston.format.json()),transports: [new winston.transports.Console(),],});// 添加 trace 信息到日志logger.add(new winston.transports.Console({format: winston.format.printf((info) => {const span = trace.getActiveSpan();if (span) {const spanContext = span.spanContext();info.trace_id = spanContext.traceId;info.span_id = spanContext.spanId;}return JSON.stringify(info);}),}));// 使用示例function example() {const span = trace.getActiveSpan();logger.info('Processing request', {user_id: '12345',action: 'create',});}
集成 Pino
const pino = require('pino');const { trace } = require('@opentelemetry/api');// 创建 loggerconst logger = pino({formatters: {log: (object) => {const span = trace.getActiveSpan();if (span) {const spanContext = span.spanContext();object.trace_id = spanContext.traceId;object.span_id = spanContext.spanId;}return object;},},});// 使用示例function example() {logger.info({user_id: '12345',action: 'create',}, 'Processing request');}
框架集成示例
Express Framework
基础集成
const express = require('express');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider({resource: new Resource({[SemanticResourceAttributes.SERVICE_NAME]: 'my-service',}),});provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter({url: 'uptrace-grpc.yodao.cn:4317',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},})));provider.register();// 创建 Express 应用const app = express();// 使用自动埋点app.use(express.json());app.get('/users/:id', async (req, res) => {const { trace, context } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = trace.getActiveSpan();span?.setAttributes({'user.id': req.params.id,});// 业务逻辑const user = await getUser(req.params.id);res.json(user);});app.listen(3000, () => {console.log('Server running on port 3000');});
使用自动埋点库
// 使用 @opentelemetry/auto-instrumentations-noderequire('@opentelemetry/auto-instrumentations-node/register');const express = require('express');const app = express();app.get('/users/:id', async (req, res) => {const user = await getUser(req.params.id);res.json(user);});app.listen(3000);
Koa Framework
const Koa = require('koa');const Router = require('@koa/router');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');const { KoaInstrumentation } = require('@opentelemetry/instrumentation-koa');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter({url: 'uptrace-grpc.yodao.cn:4317',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},})));provider.register();// 创建 Koa 应用const app = new Koa();const router = new Router();router.get('/users/:id', async (ctx) => {const { trace } = require('@opentelemetry/api');const span = trace.getActiveSpan();span?.setAttributes({'user.id': ctx.params.id,});ctx.body = await getUser(ctx.params.id);});app.use(router.routes());app.listen(3000);
NestJS Framework
import { Module } from '@nestjs/common';import { Controller, Get, Param } from '@nestjs/common';import { TraceService } from '@opentelemetry/api';@Controller('users')export class UsersController {constructor(private readonly traceService: TraceService) {}@Get(':id')async getUser(@Param('id') id: string) {const tracer = this.traceService.getTracer('my-service');const span = tracer.startSpan('get_user');try {span.setAttributes({ 'user.id': id });const user = await this.userService.findOne(id);span.setStatus({ code: SpanStatusCode.OK });return user;} catch (error) {span.recordException(error);span.setStatus({ code: SpanStatusCode.ERROR });throw error;} finally {span.end();}}}
Next.js Framework
App Router (Next.js 13+)
// app/layout.jsimport { OpenTelemetryProvider } from './otel-provider';export default function RootLayout({ children }) {return (<html><body><OpenTelemetryProvider>{children}</OpenTelemetryProvider></body></html>);}
// app/otel-provider.js'use client';import { useEffect } from 'react';import { init } from '@opentelemetry/sdk-web';export function OpenTelemetryProvider({ children }) {useEffect(() => {init({serviceName: 'my-nextjs-app',exporterUrl: 'https://api.uptrace.dev:4318/v1/traces',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},});}, []);return <>{children}</>;}
Pages Router
// pages/_app.jsimport { useEffect } from 'react';import { init } from '@opentelemetry/sdk-web';function MyApp({ Component, pageProps }) {useEffect(() => {init({serviceName: 'my-nextjs-app',exporterUrl: 'https://api.uptrace.dev:4318/v1/traces',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},});}, []);return <Component {...pageProps} />;}export default MyApp;
gRPC Server
const grpc = require('@grpc/grpc-js');const protoLoader = require('@grpc/proto-loader');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { GrpcInstrumentation } = require('@opentelemetry/instrumentation-grpc');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.register();// 加载 proto 文件const packageDefinition = protoLoader.loadSync('user.proto');const userProto = grpc.loadPackageDefinition(packageDefinition).user;// 实现服务function getUser(call, callback) {const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = tracer.startSpan('get_user');span.setAttributes({'user.id': call.request.id,});// 业务逻辑const user = { id: call.request.id, name: 'John Doe' };span.setStatus({ code: 1 }); // OKspan.end();callback(null, user);}// 创建服务器const server = new grpc.Server();server.addService(userProto.UserService.service, { getUser });server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {server.start();});
Database - PostgreSQL (pg)
const { Pool } = require('pg');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { PgInstrumentation } = require('@opentelemetry/instrumentation-pg');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.register();// 创建数据库连接池const pool = new Pool({host: 'localhost',port: 5432,database: 'mydb',user: 'user',password: 'password',});// 查询示例async function getUser(id) {const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = tracer.startSpan('database.query');try {const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);span.setAttributes({'db.system': 'postgresql','db.statement': 'SELECT * FROM users WHERE id = $1',});return result.rows[0];} catch (error) {span.recordException(error);span.setStatus({ code: 2 }); // ERRORthrow error;} finally {span.end();}}
Database - MongoDB
const { MongoClient } = require('mongodb');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.register();// 连接 MongoDBconst client = new MongoClient('mongodb://localhost:27017');async function getUser(id) {const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = tracer.startSpan('mongodb.query');try {await client.connect();const db = client.db('mydb');const user = await db.collection('users').findOne({ _id: id });span.setAttributes({'db.system': 'mongodb','db.name': 'mydb','db.collection': 'users',});return user;} catch (error) {span.recordException(error);throw error;} finally {span.end();}}
Redis
const redis = require('redis');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.register();// 创建 Redis 客户端const client = redis.createClient({url: 'redis://localhost:6379',});async function getCache(key) {const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = tracer.startSpan('redis.get');try {await client.connect();const value = await client.get(key);span.setAttributes({'db.system': 'redis','db.operation': 'GET','db.redis.key': key,});return value;} catch (error) {span.recordException(error);throw error;} finally {span.end();}}
Kafka
const { Kafka } = require('kafkajs');const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');const { KafkaJsInstrumentation } = require('@opentelemetry/instrumentation-kafkajs');// 初始化 OpenTelemetryconst provider = new NodeTracerProvider();provider.register();// 创建 Kafka 客户端const kafka = new Kafka({clientId: 'my-app',brokers: ['localhost:9092'],});const producer = kafka.producer();async function sendMessage(topic, message) {const { trace } = require('@opentelemetry/api');const tracer = trace.getTracer('my-service');const span = tracer.startSpan('kafka.produce');try {await producer.connect();await producer.send({topic,messages: [{ value: JSON.stringify(message) }],});span.setAttributes({'messaging.system': 'kafka','messaging.destination': topic,'messaging.operation': 'publish',});} catch (error) {span.recordException(error);throw error;} finally {span.end();}}
高级配置
自定义采样策略
const { TraceIdRatioBasedSampler, ParentBasedSampler } = require('@opentelemetry/sdk-trace-base');// 基于 Span 名称的自定义采样器class CustomSampler {constructor(defaultRate = 0.1) {this.defaultSampler = new ParentBasedSampler({root: new TraceIdRatioBasedSampler(defaultRate),});this.alwaysSample = ['/checkout', '/payment'];this.neverSample = ['/health', '/metrics'];}shouldSample(context, traceId, spanName) {// 总是采样if (this.alwaysSample.some(name => spanName.includes(name))) {return { decision: SamplingDecision.RECORD_AND_SAMPLE };}// 从不采样if (this.neverSample.some(name => spanName.includes(name))) {return { decision: SamplingDecision.NOT_RECORD };}// 使用默认采样策略return this.defaultSampler.shouldSample(context, traceId, spanName);}}// 使用自定义采样器const provider = new NodeTracerProvider({sampler: new CustomSampler(0.1),});
多 Exporter 配置
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-base');// 创建多个 exporterconst otlpExporter = new OTLPTraceExporter({url: 'uptrace-grpc.yodao.cn:4317',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},});const jaegerExporter = new JaegerExporter({endpoint: 'http://localhost:14268/api/traces',});const consoleExporter = new ConsoleSpanExporter();// 添加多个处理器provider.addSpanProcessor(new BatchSpanProcessor(otlpExporter));provider.addSpanProcessor(new BatchSpanProcessor(jaegerExporter));provider.addSpanProcessor(new BatchSpanProcessor(consoleExporter));
性能优化配置
const provider = new NodeTracerProvider({resource: new Resource({[SemanticResourceAttributes.SERVICE_NAME]: 'my-service',}),});provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter({url: 'uptrace-grpc.yodao.cn:4317',headers: {'uptrace-dsn': 'http://token@uptrace-grpc.yodao.cn/project',},// 启用压缩compression: 'gzip',// 超时时间timeoutMillis: 10000,}),{// 批量大小maxExportBatchSize: 512,// 批量超时scheduledDelayMillis: 5000,// 队列大小maxQueueSize: 2048,// 导出超时exportTimeoutMillis: 30000,}));
最佳实践
1. Context 传递
// ✅ 正确:传递 contextasync function processOrder(context, orderId) {const tracer = trace.getTracer('my-service');const span = tracer.startSpan('process_order', undefined, context);// 传递 context 给下游函数await validateOrder(context, orderId);await chargePayment(context, orderId);span.end();}// ❌ 错误:创建新的 contextasync function processOrder(context, orderId) {const tracer = trace.getTracer('my-service');const span = tracer.startSpan('process_order');// 不要创建新的 contextawait validateOrder(context.active(), orderId);span.end();}
2. 异步操作
// ✅ 正确:使用 async/awaitasync function getUser(id) {const span = tracer.startSpan('get_user');try {const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);span.setStatus({ code: SpanStatusCode.OK });return user;} catch (error) {span.recordException(error);span.setStatus({ code: SpanStatusCode.ERROR });throw error;} finally {span.end();}}
3. 错误处理
// ✅ 正确:记录错误并设置状态try {await processOrder(orderId);span.setStatus({ code: SpanStatusCode.OK });} catch (error) {span.recordException(error);span.setStatus({code: SpanStatusCode.ERROR,message: error.message,});throw error;}
4. 资源管理
// ✅ 正确:使用 try-finally 确保 span 结束function operation() {const span = tracer.startSpan('operation');try {// 业务逻辑} finally {span.end();}}
故障排查
常见问题
1. 数据未发送到后端
排查步骤:
- 检查环境变量:
echo $OTEL_SERVICE_NAMEecho $OTEL_EXPORTER_OTLP_ENDPOINT
- 启用调试日志:
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
- 使用 Console Exporter 验证:
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-base');provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));
2. Span 未关联
确保正确传递 context:
// 使用 context.with() 传递const newContext = context.with(trace.setSpan(context.active(), span));await downstreamFunction(newContext);
3. 内存泄漏
检查是否有未结束的 span:
// 使用 try-finally 确保 span 结束const span = tracer.startSpan('operation');try {// 业务逻辑} finally {span.end();}
调试技巧
启用详细日志
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
使用 Console Exporter
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-base');provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()));
下一步
参考资源
- OpenTelemetry JavaScript Documentation
- OpenTelemetry JS SDK
- OpenTelemetry JS Contrib
- Node.js Semantic Conventions