Visitar URL original
lib: add built-in OpenTelemetry tracing by bengl · Pull Request #66587 · nodejs/node · GitHub
Skip to content

lib: add built-in OpenTelemetry tracing - #66587

Open
bengl wants to merge 1 commit into
nodejs:mainfrom
bengl:bengl/otel-2
Open

bengl wants to merge 1 commit into
nodejs:mainfrom
bengl:bengl/otel-2

Conversation

@bengl

@bengl bengl commented Oct 8, 2026

Copy link
Copy Markdown
Member

Adds experimental OpenTelemetry tracing support, as described in the included docs. Tracing is activated via environment variables only and requires the --experimental-otel flag.

There is no programmatic API yet. Custom instrumentation is not supported.

Assisted-by: pi:glm-5.3


This is a re-hash of #61907 restricting it to the OOTB behaviour only, leaving any API surface as an exercise for future PRs (and decisions around them). All configuration is currently through env vars. Most if not all still-relevant review comments from the previous PR are addressed here. In addition, a benchmark is added to measure future improvements.

Metrics and logging signals are also left to future PRs.

Adds experimental OpenTelemetry tracing support, as described in the
included docs. Tracing is activated via environment variables only and
requires the --experimental-otel flag.

There is no programmatic API yet. Custom instrumentation is not
supported.

Assisted-by: pi:glm-5.3
Signed-off-by: Bryan English <bryan@bryanenglish.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Oct 8, 2026
@bengl
bengl requested review from Flarna and Qard October 8, 2026 05:09
@bengl bengl mentioned this pull request Oct 8, 2026
Comment thread doc/api/cli.md
Comment on lines +4451 to +4466
### `NODE_OTEL_ENDPOINT=url`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

When set to a non-empty value while the [`--experimental-otel`][] flag is
enabled, activates the built-in OpenTelemetry tracing subsystem and directs
spans to the specified OTLP/HTTP collector endpoint. The endpoint must be
the base URL of the collector: any path present in the endpoint is
replaced with `/v1/traces`, and an endpoint that already ends with
`/v1/traces` is used as is. If `NODE_OTEL` is also set, `NODE_OTEL_ENDPOINT`
takes precedence for the endpoint. See the [OpenTelemetry][] documentation
for details.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should also mention the default is http://localhost:4318?

Comment thread lib/internal/otel/core.js
filter);
}

function start(options = { __proto__: null }) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Require and use kEmptyObject from internal/util?

Suggested change
function start(options = { __proto__: null }) {
function start(options = kEmptyObject) {

Comment thread doc/api/otel.md
Comment on lines +56 to +67
### `NODE_OTEL_ENDPOINT`

When set to a non-empty value, activates the tracing subsystem and directs
spans to the specified OTLP collector endpoint. The endpoint should be the
base URL of an OTLP/HTTP collector (e.g. `http://localhost:4318`) without
a path: any path present in the endpoint is replaced with `/v1/traces`,
and an endpoint that already ends with `/v1/traces` is used as is.

```bash
NODE_OTEL_ENDPOINT=http://collector.example.com:4318 \
node --experimental-otel app.js
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, I think it should explicitly mention the default, even though the NODE_OTEL section mentions its default

Maybe

Suggested change
### `NODE_OTEL_ENDPOINT`
When set to a non-empty value, activates the tracing subsystem and directs
spans to the specified OTLP collector endpoint. The endpoint should be the
base URL of an OTLP/HTTP collector (e.g. `http://localhost:4318`) without
a path: any path present in the endpoint is replaced with `/v1/traces`,
and an endpoint that already ends with `/v1/traces` is used as is.
```bash
NODE_OTEL_ENDPOINT=http://collector.example.com:4318 \
node --experimental-otel app.js
```
### `NODE_OTEL_ENDPOINT`
When set to a non-empty value, activates the tracing subsystem and directs
spans to the specified OTLP collector endpoint. The endpoint should be the
base URL of an OTLP/HTTP collector (defaults to `http://localhost:4318`) without
a path: any path present in the endpoint is replaced with `/v1/traces`,
and an endpoint that already ends with `/v1/traces` is used as is.
```bash
NODE_OTEL_ENDPOINT=http://collector.example.com:4318 \
node --experimental-otel app.js
```

@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.90751% with 84 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.44%. Comparing base (7514ef8) to head (a6861a5).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/otel/flush.js 88.88% 39 Missing and 1 partial ⚠️
lib/internal/otel/instrumentations.js 90.60% 21 Missing and 4 partials ⚠️
lib/internal/otel/span.js 94.31% 8 Missing and 2 partials ⚠️
lib/internal/otel/core.js 93.33% 8 Missing ⚠️
lib/internal/process/pre_execution.js 98.24% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66587      +/-   ##
==========================================
- Coverage   92.79%   90.44%   -2.35%     
==========================================
  Files         422      796     +374     
  Lines      193669   277600   +83931     
  Branches    29886    53321   +23435     
==========================================
+ Hits       179712   251082   +71370     
- Misses      13630    16921    +3291     
- Partials      327     9597    +9270     
Files with missing lines Coverage Δ
lib/internal/otel/id.js 100.00% <100.00%> (ø)
src/node_options.cc 81.73% <100.00%> (ø)
src/node_options.h 95.67% <100.00%> (ø)
lib/internal/process/pre_execution.js 96.61% <98.24%> (+19.02%) ⬆️
lib/internal/otel/core.js 93.33% <93.33%> (ø)
lib/internal/otel/span.js 94.31% <94.31%> (ø)
lib/internal/otel/instrumentations.js 90.60% <90.60%> (ø)
lib/internal/otel/flush.js 88.88% <88.88%> (ø)

... and 496 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jasnell jasnell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass... Generally looks reasonable. Main concern is on instrumentation cost. Left a couple comments.

Comment thread lib/internal/otel/span.js
}

setAttribute(key, value) {
this.#attributes[key] = value;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#attributes is going to be put into a slow dictionary mode with a internal shape transition every time an attribute is set... Meaning this is going to have a non-trivial cost.

Could this use a SafeMap instead?

Obviously that makes getAttributes trickier below but I would assume that setAttribute is the hotter path?

Comment thread lib/internal/otel/span.js
}

addEvent(name, attributes) {
ArrayPrototypePush(this.#events, {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many events might we expect? If it's a lot, or completely unbounded, this becomes quadratic after a while.

Comment thread doc/api/otel.md
Comment on lines +25 to +32
The subsystem is independent of the OpenTelemetry JavaScript packages. It
is not interoperable with `@opentelemetry/api`, and spans created by one
are not visible to the other. Running both in the same process produces
duplicate trace and span IDs. The built-in instrumentation also
overwrites the `traceparent` (and, when present, `tracestate`) header on
outgoing HTTP requests, discarding whatever a userland propagator may
have set. Users should run either the built-in subsystem or a userland
OpenTelemetry SDK, not both.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I wonder what the purpose of this is. From my perspective, this feature should work in one of two ways:

  1. Provides an implementation of the API such that compliant instrumentations can register with it and it delivers the appropriate signals data to a collector.
  2. Registers with whatever OTEL implementation is present such that the signals data is inlined correctly.

With the caveats highlighted in this paragraph, I'm just not clear what this accomplishes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants