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/api
npm 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-service
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
# OTLP Exporter(内部平台)
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export 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 平台查看数据:

  1. Traces: 查看调用链路
  2. Metrics: 查看请求量、延迟等指标
  3. 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/api
npm install --save @opentelemetry/sdk-node
npm install --save @opentelemetry/exporter-trace-otlp-grpc
npm install --save @opentelemetry/exporter-metrics-otlp-grpc
npm install --save @opentelemetry/resources
npm install --save @opentelemetry/semantic-conventions

基础初始化

创建 OpenTelemetry 初始化文件

// otel.js
const { 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.js
const { 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 }); // OK
span.setAttribute('order.success', true);
} catch (error) {
span.recordException(error);
span.setStatus({ code: 2, message: error.message }); // ERROR
throw 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 headers
propagation.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, // SERVER
attributes: {
'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');
// 创建 logger
const 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');
// 创建 logger
const 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');
// 初始化 OpenTelemetry
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',
},
})
)
);
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-node
require('@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');
// 初始化 OpenTelemetry
const 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.js
import { 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.js
import { 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');
// 初始化 OpenTelemetry
const 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 }); // OK
span.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');
// 初始化 OpenTelemetry
const 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 }); // ERROR
throw error;
} finally {
span.end();
}
}

Database - MongoDB

const { MongoClient } = require('mongodb');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');
// 初始化 OpenTelemetry
const provider = new NodeTracerProvider();
provider.register();
// 连接 MongoDB
const 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');
// 初始化 OpenTelemetry
const 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');
// 初始化 OpenTelemetry
const 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');
// 创建多个 exporter
const 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 传递

// ✅ 正确:传递 context
async 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();
}
// ❌ 错误:创建新的 context
async function processOrder(context, orderId) {
const tracer = trace.getTracer('my-service');
const span = tracer.startSpan('process_order');
// 不要创建新的 context
await validateOrder(context.active(), orderId);
span.end();
}

2. 异步操作

// ✅ 正确:使用 async/await
async 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. 数据未发送到后端

排查步骤:

  1. 检查环境变量:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
  1. 启用调试日志:
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
  1. 使用 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())
);

下一步

参考资源

在Gitlab上编辑本文档