feat: add TracingChannel support for native APM instrumentation
Merged
LO
7mo ago+1134+1134+0123456789012345678901234567890123456789-19-19-01234567890123456789
Summary
- Add first-class
TracingChannel(node:diagnostics_channel) support so APM tools (OTEL, Datadog, Sentry) can instrument mysql2 without monkey-patching via IITM/RITM - Four tracing channels:
mysql2:query,mysql2:execute,mysql2:connect,mysql2:pool:connect - Context fields map to OTEL semantic conventions (
db.query.text,db.namespace,server.address,server.port) - Zero-cost when no subscribers —
hasSubscriberschecked before any context allocation - Graceful degradation on Node versions without
TracingChannel(e.g. Node 18)
Motivation
Current APM instrumentations use IITM (import-in-the-middle) for ESM and RITM (require-in-the-middle) for CJS to monkey-patch Connection.prototype.query and Connection.prototype.execute. This has fragility concerns:
- Runtime lock-in: RITM/IITM rely on Node.js-specific module loader internals — they don't work on Bun or Deno
- ESM fragility: IITM is built on Node.js's evolving module customization hooks, a persistent source of breakage in the OTEL JS ecosystem
- Initialization ordering: instrumentation must be set up before mysql2 is first imported — get the order wrong and instrumentation silently does nothing
- Bundling: users must externalize instrumented modules, increasingly difficult with server-side bundling
With native TracingChannel support, instrumentation libraries become subscribers, not patches. Each tool listens independently with no ordering concerns, no clobbering, and no internal API dependency.
Channels
| TracingChannel | Tracks | Context fields |
|---|---|---|
mysql2:query | connection.query() execution | query, values, database, serverAddress, serverPort |
mysql2:execute | connection.execute() prepared statements | query, values, database, serverAddress, serverPort |
mysql2:connect | Client connection lifecycle | database, serverAddress, serverPort, user |
mysql2:pool:connect | Pool connection acquisition | database, serverAddress, serverPort |
Implementation
- New
lib/tracing.js+lib/tracing.d.ts— channel setup, context types,tracePromisewrappers lib/base/connection.js— instrumentedquery(),execute(), and constructor (connect)lib/base/pool.js— instrumentedgetConnection()typings/mysql/index.d.ts— re-exports context types for APM consumerstest/integration/tracing-channel.test.mts— integration tests for all 4 channels
Uses tracePromise with promise wrapping (same pattern as node-redis, ioredis, pg implementations). Promise wrapper in lib/promise/ delegates to the base layer, so promise API gets tracing for free.
Prior art
Follows the same pattern adopted by:
- undici (Node.js core) — ships
TracingChannelsince Node 20.12 - node-redis — redis/node-redis#3195
- ioredis — redis/ioredis#2089
- pg — brianc/node-postgres#3624
closes #4174