Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6a9412f
errors: handle V8 warnings in DisallowJavascriptExecutionScope
DivyanshuX9 May 24, 2026
7b20b8a
Merge branch 'nodejs:main' into fix/issue-63473-repl-asm-warning
DivyanshuX9 May 29, 2026
a786501
lib: diagnostics_channel use AsyncLocalStorage for suppression context
DivyanshuX9 May 29, 2026
e4aea85
test: add diagnostics_channel suppression coverage
DivyanshuX9 May 29, 2026
a5c4940
lib: rename diagnostics_channel suppression option to subscriberId
DivyanshuX9 May 30, 2026
7e415c0
test: clean up diagnostics_channel suppression coverage
DivyanshuX9 May 30, 2026
8e6d7cb
test: simplify diagnostics_channel suppression handler
DivyanshuX9 May 30, 2026
07e7e97
test: finalize diagnostics_channel suppression test
DivyanshuX9 May 30, 2026
9468795
diag(suppression-als): use ALS.run bound fn for suppression context; …
DivyanshuX9 May 30, 2026
b8194af
test: wrap suppressed callbacks with common.mustCall
DivyanshuX9 May 30, 2026
66e0cc9
src: emit V8 warnings synchronously in PerIsolateMessageListener (rem…
DivyanshuX9 May 30, 2026
01d9cc0
src: reintroduce synchronous V8 warning emission (USE wrapper)
DivyanshuX9 May 30, 2026
f9f428a
Merge branch 'main' into diag/suppression-als
DivyanshuX9 May 30, 2026
ddf8179
lib: initialize suppression storage eagerly
DivyanshuX9 May 31, 2026
909f0fc
Revert "lib: initialize suppression storage eagerly"
DivyanshuX9 May 31, 2026
e630f72
src: defer V8 warning emission to next tick
DivyanshuX9 May 31, 2026
cd5c009
lib: unify bypass key validation in diagnostics_channel
DivyanshuX9 Jun 3, 2026
3fdcd8e
test: update diagnostics_channel suppression tests for lazy storage
DivyanshuX9 Jun 3, 2026
716a4eb
test: fix lint errors in diagnostics_channel suppression tests
DivyanshuX9 Jun 4, 2026
baa6ee1
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 4, 2026
5bc0fb8
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 4, 2026
1e854d2
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 5, 2026
f3fc5cf
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 6, 2026
993bc0d
lib,test: rename suppressed/subscriberId to bypass/bypassId in diagno…
DivyanshuX9 Jun 8, 2026
24a87f4
lib: rename withSuppressionsContext to withBypassContext in diagnosti…
DivyanshuX9 Jun 8, 2026
614bc7e
lib,test: propagate bypass context through TracingChannel trace methods
DivyanshuX9 Jun 14, 2026
fcef870
lib: remove unnecessary TracingChannel bypass tracking
DivyanshuX9 Jun 14, 2026
ec9316b
lib: use kEmptyObject for options defaults in diagnostics_channel
DivyanshuX9 Jun 14, 2026
64ac224
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 14, 2026
805045d
Merge branch 'nodejs:main' into diag/suppression-als
DivyanshuX9 Jun 26, 2026
365ab7b
diagnostics_channel: add bypass subscriber/store support
DivyanshuX9 Jul 2, 2026
149f073
doc: add diagnostics_channel bypass API documentation
DivyanshuX9 Jul 6, 2026
f7f593e
doc: address Flarna review feedback on bypass documentation
DivyanshuX9 Jul 8, 2026
2fccc37
Merge branch 'main' into diag/suppression-als
DivyanshuX9 Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 224 additions & 8 deletions doc/api/diagnostics_channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,150 @@ diagnostics_channel.subscribe('my-channel', onMessage);
diagnostics_channel.unsubscribe('my-channel', onMessage);
```

#### `diagnostics_channel.bypass(key, fn[, thisArg[, ...args]])`

<!-- YAML
added: REPLACEME
-->

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.

Maybe mark it as Stability: 1.1 - Active development (or experimental), similar as tracing channels.

> Stability: 1.1 - Active development

* `key` {symbol|Object} The bypass identity token. Subscribers and
stores registered with the same `bypassId` value will be skipped
while this function executes.
* `fn` {Function} The function to run with bypass active.
* `thisArg` {any} The receiver to use for the function call.
* `...args` {any} Optional arguments to pass to the function.
* Returns: {any} The return value of `fn`.

Calls `fn` and skips any channel subscribers or bound stores that
were registered with a matching `bypassId` key. The skip behavior
also applies to any async continuations (Promises, timers,
microtasks) within `fn`.

```mjs
import diagnostics_channel from 'node:diagnostics_channel';
import { request } from 'node:http';

const { channel, bypass } = diagnostics_channel;

// A unique token identifying this APM tool
const kMyTracer = Symbol('my-tracer');

// Subscribe to HTTP requests, but opt into bypass
channel('http.client.request.start').subscribe((message) => {
console.log('HTTP request:', message.url);
}, { bypassId: kMyTracer });

// When exporting traces internally, use bypass() so the
// above subscriber is NOT triggered for this internal request
function exportTraces(data) {
bypass(kMyTracer, () => {
// This HTTP request will NOT trigger the subscriber above
const req = request('https://my-apm-backend.example.com/traces', {
method: 'POST',
});
req.end();
});
}
```

```cjs
const diagnostics_channel = require('node:diagnostics_channel');
const { request } = require('node:http');

const { channel, bypass } = diagnostics_channel;

// A unique token identifying this APM tool
const kMyTracer = Symbol('my-tracer');

// Subscribe to HTTP requests, but opt into bypass
channel('http.client.request.start').subscribe((message) => {
console.log('HTTP request:', message.url);
}, { bypassId: kMyTracer });

// When exporting traces internally, use bypass() so the
// above subscriber is NOT triggered for this internal request
function exportTraces(data) {
bypass(kMyTracer, () => {
// This HTTP request will NOT trigger the subscriber above
const req = request('https://my-apm-backend.example.com/traces', {
method: 'POST',
});
req.end();
});
}
```

Without `bypass()`, the internal `exportTraces()` HTTP request
would trigger the subscriber, which would try to export a trace
of the export, causing infinite recursion.

The bypass context propagates across async boundaries:

```mjs
// bypass() works across Promise boundaries
await bypass(kMyTracer, async () => {
await someAsyncOperation(); // still bypassed
channel('http.client.request.start').publish({}); // subscriber skipped
});

// And across timers
bypass(kMyTracer, () => {
setImmediate(() => {
// Still bypassed here
channel('http.client.request.start').publish({});
});
});
```

```cjs
(async () => {
await bypass(kMyTracer, async () => {
await someAsyncOperation();
channel('http.client.request.start').publish({});
});
bypass(kMyTracer, () => {
setImmediate(() => {
channel('http.client.request.start').publish({});
});
});
})();
```

Multiple tools can each use their own `bypassId` without
interfering with each other:

```mjs
const kToolA = Symbol('tool-a');
const kToolB = Symbol('tool-b');

channel('http.client.request.start').subscribe(handlerA, { bypassId: kToolA });
channel('http.client.request.start').subscribe(handlerB, { bypassId: kToolB });

// Only handlerA is skipped, handlerB still fires
bypass(kToolA, () => {
channel('http.client.request.start').publish({});
});
```

```cjs
const diagnostics_channel = require('node:diagnostics_channel');

const { channel, bypass } = diagnostics_channel;

const kToolA = Symbol('tool-a');
const kToolB = Symbol('tool-b');

channel('http.client.request.start').subscribe(handlerA, { bypassId: kToolA });
channel('http.client.request.start').subscribe(handlerB, { bypassId: kToolB });

// Only handlerA is skipped, handlerB still fires
bypass(kToolA, () => {
channel('http.client.request.start').publish({});
});
```

#### `diagnostics_channel.tracingChannel(nameOrChannels)`

<!-- YAML
Expand Down Expand Up @@ -414,13 +558,16 @@ channel.publish({
});
```

#### `channel.subscribe(onMessage)`
#### `channel.subscribe(onMessage[, options])`

<!-- YAML
added:
- v15.1.0
- v14.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63651
description: Added `options.bypassId` parameter.
- version:
- v24.8.0
- v22.20.0
Expand All @@ -436,6 +583,10 @@ changes:
* `onMessage` {Function} The handler to receive channel messages
* `message` {any} The message data
* `name` {string|symbol} The name of the channel
* `options` {Object}
* `bypassId` {symbol|Object} An optional identity token. When
provided, this subscriber will be skipped while
[`diagnostics_channel.bypass()`][] is active with the same key.

Register a message handler to subscribe to this channel. This message handler
will be run synchronously whenever a message is published to the channel. Any
Expand All @@ -461,6 +612,8 @@ channel.subscribe((message, name) => {
});
```

See [`diagnostics_channel.bypass()`][] for usage with `bypassId`.

#### `channel.unsubscribe(onMessage)`

<!-- YAML
Expand Down Expand Up @@ -520,18 +673,26 @@ channel.subscribe(onMessage);
channel.unsubscribe(onMessage);
```

#### `channel.bindStore(store[, transform])`
#### `channel.bindStore(store[, transform[, options]])`

<!-- YAML
added:
- v19.9.0
- v18.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63651
description: Added `options.bypassId` parameter.
-->

> Stability: 1 - Experimental

* `store` {AsyncLocalStorage} The store to which to bind the context data
* `transform` {Function} Transform context data before setting the store context
* `options` {Object}
* `bypassId` {symbol|Object} An optional identity token. When
provided, this bound store will be skipped while
[`diagnostics_channel.bypass()`][] is active with the same key.

When [`channel.runStores(context, ...)`][] is called, the given context data
will be applied to any store bound to the channel. If the store has already been
Expand Down Expand Up @@ -565,6 +726,8 @@ channel.bindStore(store, (data) => {
});
```

See [`diagnostics_channel.bypass()`][] for usage with `bypassId`.

#### `channel.unbindStore(store)`

<!-- YAML
Expand Down Expand Up @@ -756,7 +919,7 @@ simplify the process of producing events for tracing application flow.
single `TracingChannel` at the top-level of the file rather than creating them
dynamically.

#### `tracingChannel.subscribe(subscribers)`
#### `tracingChannel.subscribe(subscribers[, options])`

<!-- YAML
added:
Expand All @@ -770,6 +933,11 @@ added:
* `asyncStart` {Function} The [`asyncStart` event][] subscriber
* `asyncEnd` {Function} The [`asyncEnd` event][] subscriber
* `error` {Function} The [`error` event][] subscriber
* `options` {Object}
* `bypassId` {symbol|Object} An optional identity token. When
provided, ALL handlers (start, end, asyncStart, asyncEnd, error)
will be skipped while [`diagnostics_channel.bypass()`][] is
active with the same key.

Helper to subscribe a collection of functions to the corresponding channels.
This is the same as calling [`channel.subscribe(onMessage)`][] on each channel
Expand Down Expand Up @@ -823,6 +991,52 @@ channels.subscribe({
});
```

To opt all TracingChannel handlers into bypass behavior:

```mjs
import diagnostics_channel from 'node:diagnostics_channel';

const { tracingChannel, bypass } = diagnostics_channel;
const kMyTool = Symbol('my-tool');
const tc = tracingChannel('my-operation');

// All TracingChannel events skipped when bypass(kMyTool) is active
tc.subscribe({
start(message) { /* ... */ },
end(message) { /* ... */ },
asyncStart(message) { /* ... */ },
asyncEnd(message) { /* ... */ },
}, { bypassId: kMyTool });

bypass(kMyTool, () => {
tc.traceSync(() => {
// Start and end handlers are NOT called
});
});
```

```cjs
const diagnostics_channel = require('node:diagnostics_channel');

const { tracingChannel, bypass } = diagnostics_channel;
const kMyTool = Symbol('my-tool');
const tc = tracingChannel('my-operation');

// All TracingChannel events skipped when bypass(kMyTool) is active
tc.subscribe({
start(message) { /* ... */ },
end(message) { /* ... */ },
asyncStart(message) { /* ... */ },
asyncEnd(message) { /* ... */ },
}, { bypassId: kMyTool });

bypass(kMyTool, () => {
tc.traceSync(() => {
// Start and end handlers are NOT called
});
});
```

#### `tracingChannel.unsubscribe(subscribers)`

<!-- YAML
Expand Down Expand Up @@ -954,17 +1168,18 @@ changes:
preserving their original type and methods.
- version: v26.0.0
pr-url: https://github.com/nodejs/node/pull/61766
description: Non-thenables will be returned with a warning.
description: Custom thenables will no longer be wrapped in native Promises.
Non-thenables will be returned with a warning.
-->

* `fn` {Function} Function to wrap a trace around
* `context` {Object} Shared object to correlate trace events through
* `thisArg` {any} The receiver to be used for the function call
* `...args` {any} Optional arguments to pass to the function
* Returns: {any} The return value of the given function. If the return value
is a Promise or thenable, tracing events will be published when it settles.
If the return value is not a Promise or thenable, it is returned as-is and
a warning is emitted.
* Returns: {any} The return value of the given function, or the result of
calling `.then(...)` on the return value if the tracing channel has active
subscribers. If the return value is not a Promise or thenable, then
it is returned as-is and a warning is emitted.

Trace an asynchronous function call which returns a {Promise} or
[thenable object][]. This will always produce a [`start` event][] and
Expand Down Expand Up @@ -1930,6 +2145,7 @@ Emitted when a new thread is created.
[`channel.unsubscribe(onMessage)`]: #channelunsubscribeonmessage
[`channel.withStoreScope(data)`]: #channelwithstorescopedata
[`child_process.spawn()`]: child_process.md#child_processspawncommand-args-options
[`diagnostics_channel.bypass()`]: #diagnostics_channelbypasskey-fn-thisarg-args
[`diagnostics_channel.channel(name)`]: #diagnostics_channelchannelname
[`diagnostics_channel.subscribe(name, onMessage)`]: #diagnostics_channelsubscribename-onmessage
[`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels
Expand Down
Loading
Loading