Node.js
本页面将向您展示如何在 Node.js 中开始使用 OpenTelemetry。
您将学习如何 instrument traces 和 metrics,并将它们记录到控制台。
Node.js 版 OpenTelemetry 的日志记录库仍在开发中,因此下面未提供相关示例。有关状态详情,请参阅 状态与发布。
先决条件
请确保您已在本地安装以下软件
- Node.js
- 如果您将使用 TypeScript,请安装 TypeScript。
示例应用程序
以下示例使用了一个基本的 Express 应用程序。如果您不使用 Express,也没关系 — 您也可以将 OpenTelemetry JavaScript 与其他 Web 框架(如 Koa 和 Nest.JS)一起使用。有关支持框架的库的完整列表,请参阅 注册表。
有关更详细的示例,请参阅 示例。
依赖项
首先,在一个新目录中设置一个空的 package.json 文件。
npm init -y
接下来,安装 Express 依赖项。
npm install express @types/express
npm install -D tsx # a tool to run TypeScript (.ts) files directly with node
npm install express
创建并启动 HTTP 服务器
创建一个名为 app.ts 的文件(如果未使用 TypeScript,则为 app.js)并添加以下代码:
/*app.ts*/
import express, { Express } from 'express';
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
function getRandomNumber(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
app.get('/rolldice', (req, res) => {
res.send(getRandomNumber(1, 6).toString());
});
app.listen(PORT, () => {
console.log(`Listening for requests on https://:${PORT}`);
});
/*app.js*/
const express = require('express');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
app.get('/rolldice', (req, res) => {
res.send(getRandomNumber(1, 6).toString());
});
app.listen(PORT, () => {
console.log(`Listening for requests on https://:${PORT}`);
});
使用以下命令运行应用程序,然后在浏览器中打开 https://:8080/rolldice 以确保其正常工作。
$ npx tsx app.ts
Listening for requests on https://:8080
$ node app.js
Listening for requests on https://:8080
仪表
以下内容展示了如何安装、初始化和运行一个已 instrumented 的 OpenTelemetry 应用程序。
更多依赖项
首先,安装 Node SDK 和 autoinstrumentations 包。
Node SDK 允许您使用对大多数用例都适用的默认配置来初始化 OpenTelemetry。
auto-instrumentations-node 包安装了 instrumentation 库,这些库将自动创建与库中调用的代码相对应的 spans。在这种情况下,它为 Express 提供了 instrumentation,使示例应用程序能够自动为每个传入的请求创建 spans。
npm install @opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/sdk-metrics \
@opentelemetry/sdk-trace-node
要查找所有 autoinstrumentation 模块,您可以查看 注册表。
设置
Instrumentation 的设置和配置必须在您的应用程序代码运行之前执行。一个常用的工具是 –import 标志。
创建一个名为 instrumentation.ts 的文件(如果未使用 TypeScript,则为 instrumentation.mjs),其中将包含您的 instrumentation 设置代码。
以下使用 --import instrumentation.ts (TypeScript) 的示例需要 Node.js v.20 或更高版本。如果您使用的是 Node.js v.18,请使用 JavaScript 示例。
/*instrumentation.ts*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
/*instrumentation.mjs*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
运行已 instrumented 的应用
现在您可以像往常一样运行您的应用程序,但可以使用 --import 标志在应用程序代码加载之前加载 instrumentation。确保您的 NODE_OPTIONS 环境变量中没有其他冲突的 --import 或 --require 标志,例如 --require @opentelemetry/auto-instrumentations-node/register。
$ npx tsx --import ./instrumentation.ts app.ts
Listening for requests on https://:8080
$ node --import ./instrumentation.mjs app.js
Listening for requests on https://:8080
在浏览器中打开 https://:8080/rolldice 并刷新几次页面。过一会儿,您应该会在控制台中看到由 ConsoleSpanExporter 打印的 spans。
查看示例输出
{
resource: {
attributes: {
'host.arch': 'arm64',
'host.id': '8FEBBC33-D6DA-57FC-8EF0-1A9C14B919F8',
'process.pid': 12460,
// ... some resource attributes elided ...
'process.runtime.version': '22.17.1',
'process.runtime.name': 'nodejs',
'process.runtime.description': 'Node.js',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '2.0.1'
}
},
instrumentationScope: {
name: '@opentelemetry/instrumentation-express',
version: '0.52.0',
schemaUrl: undefined
},
traceId: '61e8960c349ca2a3a51289e050fd3b82',
parentSpanContext: {
traceId: '61e8960c349ca2a3a51289e050fd3b82',
spanId: '631b666604f933bc',
traceFlags: 1,
traceState: undefined
},
traceState: undefined,
name: 'request handler - /rolldice',
id: 'd8fcc05ac4f60c99',
kind: 0,
timestamp: 1755719307779000,
duration: 2801.5,
attributes: {
'http.route': '/rolldice',
'express.name': '/rolldice',
'express.type': 'request_handler'
},
status: { code: 0 },
events: [],
links: []
}
{
resource: {
attributes: {
'host.arch': 'arm64',
'host.id': '8FEBBC33-D6DA-57FC-8EF0-1A9C14B919F8',
'process.pid': 12460,
// ... some resource attributes elided ...
'process.runtime.version': '22.17.1',
'process.runtime.name': 'nodejs',
'process.runtime.description': 'Node.js',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '2.0.1'
}
},
instrumentationScope: {
name: '@opentelemetry/instrumentation-http',
version: '0.203.0',
schemaUrl: undefined
},
traceId: '61e8960c349ca2a3a51289e050fd3b82',
parentSpanContext: undefined,
traceState: undefined,
name: 'GET /rolldice',
id: '631b666604f933bc',
kind: 1,
timestamp: 1755719307777000,
duration: 4705.75,
attributes: {
'http.url': 'https://:8080/rolldice',
'http.host': 'localhost:8080',
'net.host.name': 'localhost',
'http.method': 'GET',
'http.scheme': 'http',
'http.target': '/rolldice',
'http.user_agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:141.0) Gecko/20100101 Firefox/141.0',
'http.flavor': '1.1',
'net.transport': 'ip_tcp',
'net.host.ip': '::ffff:127.0.0.1',
'net.host.port': 8080,
'net.peer.ip': '::ffff:127.0.0.1',
'net.peer.port': 63067,
'http.status_code': 200,
'http.status_text': 'OK',
'http.route': '/rolldice'
},
status: { code: 0 },
events: [],
links: []
}
生成的 span 跟踪了对 /rolldice 路由的请求的生命周期。
向该端点发送更多请求。片刻之后,您将在控制台输出中看到 metrics,例如以下内容:
查看示例输出
{
descriptor: {
name: 'http.server.duration',
type: 'HISTOGRAM',
description: 'Measures the duration of inbound HTTP requests.',
unit: 'ms',
valueType: 1,
advice: {}
},
dataPointType: 0,
dataPoints: [
{
attributes: {
'http.scheme': 'http',
'http.method': 'GET',
'net.host.name': 'localhost',
'http.flavor': '1.1',
'http.status_code': 200,
'net.host.port': 8080,
'http.route': '/rolldice'
},
startTime: [ 1755719307, 782000000 ],
endTime: [ 1755719482, 940000000 ],
value: {
min: 1.439792,
max: 5.775,
sum: 15.370167,
buckets: {
boundaries: [
0, 5, 10, 25,
50, 75, 100, 250,
500, 750, 1000, 2500,
5000, 7500, 10000
],
counts: [
0, 5, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0
]
},
count: 6
}
},
{
attributes: {
'http.scheme': 'http',
'http.method': 'GET',
'net.host.name': 'localhost',
'http.flavor': '1.1',
'http.status_code': 304,
'net.host.port': 8080,
'http.route': '/rolldice'
},
startTime: [ 1755719433, 609000000 ],
endTime: [ 1755719482, 940000000 ],
value: {
min: 1.39575,
max: 1.39575,
sum: 1.39575,
buckets: {
boundaries: [
0, 5, 10, 25,
50, 75, 100, 250,
500, 750, 1000, 2500,
5000, 7500, 10000
],
counts: [
0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0
]
},
count: 1
}
}
]
}
{
descriptor: {
name: 'nodejs.eventloop.utilization',
type: 'OBSERVABLE_GAUGE',
description: 'Event loop utilization',
unit: '1',
valueType: 1,
advice: {}
},
dataPointType: 2,
dataPoints: [
{
attributes: {},
startTime: [ 1755719362, 939000000 ],
endTime: [ 1755719482, 940000000 ],
value: 0.00843049454565211
}
]
}
{
descriptor: {
name: 'v8js.gc.duration',
type: 'HISTOGRAM',
description: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb.',
unit: 's',
valueType: 1,
advice: { explicitBucketBoundaries: [ 0.01, 0.1, 1, 10 ] }
},
dataPointType: 0,
dataPoints: [
{
attributes: { 'v8js.gc.type': 'minor' },
startTime: [ 1755719303, 5000000 ],
endTime: [ 1755719482, 940000000 ],
value: {
min: 0.0005120840072631835,
max: 0.0022552499771118163,
sum: 0.006526499509811401,
buckets: { boundaries: [ 0.01, 0.1, 1, 10 ], counts: [ 6, 0, 0, 0, 0 ] },
count: 6
}
},
{
attributes: { 'v8js.gc.type': 'incremental' },
startTime: [ 1755719310, 812000000 ],
endTime: [ 1755719482, 940000000 ],
value: {
min: 0.0003403329849243164,
max: 0.0012867081165313721,
sum: 0.0016270411014556885,
buckets: { boundaries: [ 0.01, 0.1, 1, 10 ], counts: [ 2, 0, 0, 0, 0 ] },
count: 2
}
},
{
attributes: { 'v8js.gc.type': 'major' },
startTime: [ 1755719310, 830000000 ],
endTime: [ 1755719482, 940000000 ],
value: {
min: 0.0025888750553131105,
max: 0.005744750022888183,
sum: 0.008333625078201293,
buckets: { boundaries: [ 0.01, 0.1, 1, 10 ], counts: [ 2, 0, 0, 0, 0 ] },
count: 2
}
}
]
}
下一步
使用 手动 instrumentation 丰富您自动生成的 instrumentation,以获取自定义的可观测性数据。
您还需要配置一个合适的 exporter,以便将 您的遥测数据导出到一个或多个遥测后端。
如果您想探索更复杂的示例,请查看 OpenTelemetry Demo,其中包括基于 JavaScript 的 Payment Service 和基于 TypeScript 的 Frontend Service。
故障排除
出现问题了吗?您可以启用诊断日志记录以验证 OpenTelemetry 是否已正确初始化。
/*instrumentation.ts*/
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
// const sdk = new NodeSDK({...
/*instrumentation.mjs*/
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
// const sdk = new NodeSDK({...