diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index e5ace9abcfdddc..4ca30bf0e21952 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -231,6 +231,150 @@ diagnostics_channel.subscribe('my-channel', onMessage); diagnostics_channel.unsubscribe('my-channel', onMessage); ``` +#### `diagnostics_channel.bypass(key, fn[, thisArg[, ...args]])` + + + +> 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)` > 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 @@ -565,6 +726,8 @@ channel.bindStore(store, (data) => { }); ``` +See [`diagnostics_channel.bypass()`][] for usage with `bypassId`. + #### `channel.unbindStore(store)`