Skip to content

Commit ae2b9e3

Browse files
committed
otel: add new module
Adds support for OpenTelemetry tracing, as described in the included docs. Custom instrumentation is not yet supported.
1 parent 9a237cd commit ae2b9e3

32 files changed

+3356
-1
lines changed

doc/api/cli.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3531,6 +3531,47 @@ added: v6.11.0
35313531

35323532
When set to `1`, process warnings are silenced.
35333533

3534+
### `NODE_OTEL=value`
3535+
3536+
<!-- YAML
3537+
added: REPLACEME
3538+
-->
3539+
3540+
> Stability: 1 - Experimental
3541+
3542+
When set to a non-empty value, enables the built-in OpenTelemetry tracing
3543+
subsystem using the default collector endpoint (`http://localhost:4318`). Also
3544+
makes the `node:otel` module available without the `--experimental-otel` flag.
3545+
If `NODE_OTEL_ENDPOINT` is also set, it takes precedence for the endpoint. See
3546+
the [`node:otel`][] documentation for details.
3547+
3548+
### `NODE_OTEL_ENDPOINT=url`
3549+
3550+
<!-- YAML
3551+
added: REPLACEME
3552+
-->
3553+
3554+
> Stability: 1 - Experimental
3555+
3556+
When set to a non-empty value, enables the built-in OpenTelemetry tracing
3557+
subsystem and directs spans to the specified OTLP/HTTP collector endpoint. The
3558+
`/v1/traces` path is appended automatically. Also makes the `node:otel` module
3559+
available without the `--experimental-otel` flag. See the [`node:otel`][]
3560+
documentation for details.
3561+
3562+
### `NODE_OTEL_FILTER=module[,…]`
3563+
3564+
<!-- YAML
3565+
added: REPLACEME
3566+
-->
3567+
3568+
> Stability: 1 - Experimental
3569+
3570+
Comma-separated list of core modules to instrument when OpenTelemetry tracing
3571+
is active. When not set, all supported modules are instrumented. Supported
3572+
values: `node:http`, `node:undici`, `node:fetch`. See the [`node:otel`][]
3573+
documentation for details.
3574+
35343575
### `NODE_OPTIONS=options...`
35353576

35363577
<!-- YAML
@@ -4240,6 +4281,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
42404281
[`import.meta.url`]: esm.md#importmetaurl
42414282
[`import` specifier]: esm.md#import-specifiers
42424283
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
4284+
[`node:otel`]: otel.md
42434285
[`node:sqlite`]: sqlite.md
42444286
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn
42454287
[`tls.DEFAULT_MAX_VERSION`]: tls.md#tlsdefault_max_version

doc/api/otel.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# OpenTelemetry
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
<!-- YAML
6+
added: REPLACEME
7+
-->
8+
9+
> Stability: 1 - Experimental
10+
11+
<!-- source_link=lib/otel.js -->
12+
13+
The `node:otel` module provides built-in [OpenTelemetry][] tracing support for
14+
Node.js core components. When enabled, it automatically creates spans for HTTP
15+
server and client operations and exports them using the [OTLP/HTTP JSON][]
16+
protocol.
17+
18+
To access it:
19+
20+
```cjs
21+
const otel = require('node:otel');
22+
```
23+
24+
```mjs
25+
import otel from 'node:otel';
26+
```
27+
28+
This module is only available under the `node:` scheme. It requires the
29+
`--experimental-otel` CLI flag or one of the `NODE_OTEL` / `NODE_OTEL_ENDPOINT`
30+
environment variables.
31+
32+
## Activation
33+
34+
There are two ways to activate OpenTelemetry tracing:
35+
36+
### Environment variables
37+
38+
Setting `NODE_OTEL=1` enables tracing with the default collector endpoint
39+
(`http://localhost:4318`):
40+
41+
```bash
42+
NODE_OTEL=1 node app.js
43+
```
44+
45+
To use a custom collector endpoint, set `NODE_OTEL_ENDPOINT` instead (or in
46+
addition):
47+
48+
```bash
49+
NODE_OTEL_ENDPOINT=http://collector.example.com:4318 node app.js
50+
```
51+
52+
Both variables also make the `node:otel` module available without requiring the
53+
`--experimental-otel` flag.
54+
55+
### Programmatic API
56+
57+
```cjs
58+
const otel = require('node:otel');
59+
60+
// Start with the default endpoint (http://localhost:4318):
61+
otel.start();
62+
63+
// Or with a custom endpoint:
64+
otel.start({ endpoint: 'http://collector.example.com:4318' });
65+
66+
// ... application code ...
67+
otel.stop();
68+
```
69+
70+
## Environment variables
71+
72+
### `NODE_OTEL`
73+
74+
When set to a non-empty value, enables the OpenTelemetry tracing subsystem
75+
using the default collector endpoint (`http://localhost:4318`). If
76+
`NODE_OTEL_ENDPOINT` is also set, it takes precedence.
77+
78+
### `NODE_OTEL_ENDPOINT`
79+
80+
When set to a non-empty value, enables the OpenTelemetry tracing subsystem and
81+
directs spans to the specified OTLP collector endpoint. The endpoint should be
82+
the base URL of an
83+
OTLP/HTTP collector (e.g. `http://localhost:4318`). The `/v1/traces` path is
84+
appended automatically.
85+
86+
### `NODE_OTEL_FILTER`
87+
88+
Accepts a comma-separated list of core modules to instrument. When not set, all
89+
supported modules are instrumented. For example, setting
90+
`NODE_OTEL_FILTER=node:http` would enable tracing only for the `node:http`
91+
module.
92+
93+
Supported module filter values:
94+
95+
* `node:http` — HTTP server and client operations
96+
* `node:undici` — Undici HTTP client operations
97+
* `node:fetch` — Fetch API operations (alias for undici)
98+
99+
### `OTEL_SERVICE_NAME`
100+
101+
Standard OpenTelemetry environment variable used to set the service name in
102+
exported resource attributes. Defaults to `node-<pid>`.
103+
104+
## `otel.start(options)`
105+
106+
<!-- YAML
107+
added: REPLACEME
108+
-->
109+
110+
* `options` {Object}
111+
* `endpoint` {string} The OTLP collector endpoint URL.
112+
**Default:** `'http://localhost:4318'`.
113+
* `filter` {string|string\[]} Optional comma-separated string or array of
114+
core module names to instrument (e.g. `['node:http']` or
115+
`'node:http,node:undici'`). When omitted, all supported modules are
116+
instrumented.
117+
* `maxBufferSize` {number} Maximum number of spans buffered in memory before
118+
triggering an immediate flush to the collector. Must be a positive integer.
119+
**Default:** `100`.
120+
* `flushInterval` {number} Interval in milliseconds between periodic flushes
121+
of buffered spans to the collector. Must be a positive integer.
122+
**Default:** `10000`.
123+
124+
Enables the OpenTelemetry tracing subsystem. Spans are created for supported
125+
core module operations and exported to the specified collector using the
126+
OTLP/HTTP JSON protocol.
127+
128+
If tracing is already active, calling `start()` will stop the current session
129+
and start a new one with the provided options.
130+
131+
## `otel.stop()`
132+
133+
<!-- YAML
134+
added: REPLACEME
135+
-->
136+
137+
Disables the OpenTelemetry tracing subsystem. Any buffered spans are sent to
138+
the collector before stopping. Because the export is asynchronous, delivery is
139+
not confirmed before `stop()` returns. After calling `stop()`, no new spans are
140+
created.
141+
142+
Calling `stop()` when tracing is not active is a no-op.
143+
144+
## `otel.active`
145+
146+
<!-- YAML
147+
added: REPLACEME
148+
-->
149+
150+
* {boolean}
151+
152+
Returns `true` if the OpenTelemetry tracing subsystem is currently active.
153+
154+
## Instrumented operations
155+
156+
When tracing is active, spans are automatically created for the following
157+
operations:
158+
159+
### HTTP server
160+
161+
A span with kind `SERVER` is created for each incoming HTTP request. The span
162+
starts when the request is received and ends when the response finishes. If the
163+
client disconnects before the response completes, the span ends with an error
164+
status.
165+
166+
Server spans receive error status (`STATUS_ERROR`) for 5xx response codes. 4xx
167+
responses are not treated as server errors per OpenTelemetry semantic
168+
conventions.
169+
170+
Attributes set on server spans:
171+
172+
| Attribute | Description | Condition |
173+
| --------------------------- | -------------------------------- | ----------------------------- |
174+
| `http.request.method` | HTTP method (e.g. `GET`, `POST`) | Always |
175+
| `url.path` | Request URL path (without query) | Always |
176+
| `url.query` | Query string (without `?`) | When query string is present |
177+
| `url.scheme` | `http` or `https` | Always |
178+
| `server.address` | Host header value | When `Host` header is present |
179+
| `network.protocol.version` | HTTP version (e.g. `1.1`) | Always |
180+
| `http.response.status_code` | Response status code | When response finishes |
181+
| `error.type` | HTTP status code as string | On 5xx responses |
182+
183+
### HTTP client
184+
185+
A span with kind `CLIENT` is created for each outgoing HTTP request made via
186+
`node:http`. The span starts when the request is created and ends when the
187+
response finishes or an error occurs.
188+
189+
Client spans receive error status (`STATUS_ERROR`) for 4xx and 5xx response
190+
codes. On connection errors, an `exception` event is added to the span with
191+
`exception.type`, `exception.message`, and `exception.stacktrace` attributes.
192+
193+
Attributes set on client spans:
194+
195+
| Attribute | Description | Condition |
196+
| --------------------------- | ------------------------- | ------------------------- |
197+
| `http.request.method` | HTTP method | Always |
198+
| `url.full` | Full request URL | Always |
199+
| `server.address` | Target host | Always |
200+
| `server.port` | Target port | When available |
201+
| `http.response.status_code` | Response status code | When response is received |
202+
| `network.protocol.version` | HTTP version | When response is received |
203+
| `error.type` | Status code or error name | On 4xx/5xx or errors |
204+
205+
### Undici/Fetch client
206+
207+
A span with kind `CLIENT` is created for each outgoing request made via
208+
`fetch()` or undici's `request()`.
209+
210+
Error status and `exception` event behavior is the same as for HTTP client
211+
spans above.
212+
213+
Attributes set on undici/fetch client spans:
214+
215+
| Attribute | Description | Condition |
216+
| --------------------------- | ------------------------- | ------------------------- |
217+
| `http.request.method` | HTTP method | Always |
218+
| `url.full` | Full request URL | Always |
219+
| `server.address` | Target origin | Always |
220+
| `http.response.status_code` | Response status code | When response is received |
221+
| `error.type` | Status code or error name | On 4xx/5xx or errors |
222+
223+
## W3C Trace Context propagation
224+
225+
The tracing subsystem automatically propagates [W3C Trace Context][] across HTTP
226+
boundaries:
227+
228+
* **Incoming requests**: The `traceparent` header is read from incoming HTTP
229+
requests. Child spans created during request processing inherit the trace ID.
230+
* **Outgoing requests**: The `traceparent` header is injected into outgoing HTTP
231+
and undici/fetch requests, enabling distributed tracing across services.
232+
233+
[OTLP/HTTP JSON]: https://opentelemetry.io/docs/specs/otlp/#otlphttp
234+
[OpenTelemetry]: https://opentelemetry.io/
235+
[W3C Trace Context]: https://www.w3.org/TR/trace-context/

lib/internal/bootstrap/realm.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,15 @@ const legacyWrapperList = new SafeSet([
124124
// beginning with "internal/".
125125
// Modules that can only be imported via the node: scheme.
126126
const schemelessBlockList = new SafeSet([
127+
'otel',
127128
'sea',
128129
'sqlite',
129130
'quic',
130131
'test',
131132
'test/reporters',
132133
]);
133134
// Modules that will only be enabled at run time.
134-
const experimentalModuleList = new SafeSet(['sqlite', 'quic']);
135+
const experimentalModuleList = new SafeSet(['otel', 'sqlite', 'quic']);
135136

136137
// Set up process.binding() and process._linkedBinding().
137138
{

0 commit comments

Comments
 (0)