-
Notifications
You must be signed in to change notification settings - Fork 443
Support Tracing Channels #1727
Description
I'd like to propose adding first-class TracingChannel support to tedious, following the pattern established by undici in Node.js core.
The tedious driver already provides operational visibility through a rich EventEmitter API. However, APM instrumentation libraries (OpenTelemetry, Datadog, Sentry) cannot use these events for span-based tracing today. Instead, they resort to monkey-patching Connection.prototype.execSql, Connection.prototype.callProcedure, and other methods via IITM/RITM. Here's why:
-
No async context propagation. EventEmitter listeners run in the emitter's execution context, not the caller's
AsyncLocalStoragecontext. APM tools need to create child spans of the caller's span, which requires capturing the async context at the call site. EventEmitter listeners lose this context. -
Split lifecycle. The
Requestcallback and itsdone/doneProc/errorevents are separate. To build a span, APM tools must wrap the original callback and attach additional event listeners to count statements and procedures. TracingChannel provides a unified lifecycle for the operation, so correlation is built-in. -
Multiple interception points. OTel currently patches 6 separate methods on
Connection.prototype, Each patch must independently handle callback wrapping, context propagation, and error handling. Native TracingChannel support consolidates this into structured event emission at the source.
Beyond these APM-specific gaps, the current monkey-patching approach has broader ecosystem concerns:
- Runtime lock-in: RITM and IITM rely on Node.js-specific module loader internals (
Module._resolveFilename,module.register()). They don't work on Bun or Deno, which implement the Node.js API surface but not the module loader internals. - ESM fragility: IITM is built on Node.js's module customization hooks, which are still evolving and have been a persistent source of breakage in the OTEL JS ecosystem.
- Initialization ordering: Both require instrumentation to be set up before
tediousis firstrequire()'d /import'd. - Bundling: Users must ensure instrumented modules are externalized, which is increasingly difficult as frameworks bundle server-side code into single executables or deployment files.
TracingChannel solves all of these. It provides structured lifecycle events (start, end, asyncStart, asyncEnd, error) with built-in async context propagation, zero-cost when no subscribers are attached, and a standardized subscription model that requires no monkey-patching.
I'm happy to spec this out in a PR and we can discuss it there if you feel like it's a worthwhile addition to tedious.
Prior Art
This approach follows the same pattern already adopted or in progress by other major libraries:
undici(Node.js core) — shipsTracingChannelsupport since Node 20.12:undici:requestnode-redis— redis/node-redis#3195 (node-redis:command,node-redis:connect)ioredis— redis/ioredis#2089 (ioredis:command,ioredis:connect)pg/pg-pool— brianc/node-postgres#3624 (pg:query,pg:connection,pg:pool:connect)mysql2— sidorares/node-mysql2#4178 (mysql2:query,mysql2:execute,mysql2:connect,mysql2:pool:connect) ✅ merged