Repository navigation
Add diagnostics_channel TracingChannel support - #34
MiichelleZhou wants to merge 7 commits into
Conversation
Enables instrumentation libraries (OpenTelemetry, etc.) to subscribe to structured events without monkey-patching. Uses TracingChannel for async context propagation and plain channels for simple events. Channels: - pg:query (TracingChannel) — query lifecycle with result enrichment - pg:connection (TracingChannel) — client connect lifecycle - pg:pool:connect (TracingChannel) — pool checkout lifecycle - pg:pool:release (plain) — client released back to pool - pg:pool:remove (plain) — client removed from pool All instrumentation is guarded by hasSubscribers for zero overhead when unused. Gracefully degrades to no-ops on Node < 19.9 or non-Node environments. Closes #3619 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TracingChannel is not available on Node 18 LTS. Skip the tracing-dependent tests gracefully instead of failing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node 18 backported TracingChannel but without the aggregated `hasSubscribers` getter (it returns `undefined` instead of a boolean). Raw truthiness checks treat `undefined` as "no subscribers" which silently disables tracing on Node 18. Replace all `channel.hasSubscribers` guards with `shouldTrace(channel)` which checks `hasSubscribers !== false` — treating `undefined` (Node 18) as "might have subscribers, trace unconditionally" and `false` (Node 20+) as "definitely no subscribers, skip". Also removes the now-unnecessary test skip logic since TracingChannel does exist on Node 18. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node 18 backported TracingChannel but with a buggy implementation — unsubscribing and resubscribing to the same channel crashes internally (`_subscribers` becomes undefined). Node 16 has no TracingChannel at all. Gate tests on `hasStableTracingChannel` which checks both that `dc.tracingChannel` exists AND that the aggregated `hasSubscribers` getter returns a boolean (only true on Node 19.9+/20.5+). TracingChannel tests: skipped on Node 16/18, run on Node 20+ Plain channel tests (release/remove): run on all versions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…omise type tracePromise wraps the result in a native Promise, which breaks clients configured with a custom Promise implementation (e.g. bluebird). Switch to traceCallback inside the user's this._Promise constructor so the returned promise type is always correct. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
/devin review |
PR Summary by QodoAdd diagnostics channels for pg queries, connections, and pool events
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
| @@ -1,5 +1,6 @@ | |||
| 'use strict' | |||
| const EventEmitter = require('events').EventEmitter | |||
| const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics') | |||
There was a problem hiding this comment.
🔴 Published pool package fails to load
Published installations cannot resolve require('./diagnostics') because the package excludes the new module. Importing pg-pool fails before any pool can run.
Learn more
The entry point imports a separate diagnostics module, but the files allowlist contains only index.js and esm. npm pack --dry-run --json ./packages/pg-pool confirms that diagnostics.js is omitted. Importing a published copy of the pool therefore fails before the application can create a client.
Example: An application installs the published tarball and runs require('pg-pool'); the entry point fails to resolve ./diagnostics.
Recommended fix: Add diagnostics.js to the pool package's files allowlist, then verify the packed tarball includes it.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const origCb = query.callback | ||
| const enrichedCb = (err, res) => { | ||
| if (res) context.result = { rowCount: res.rowCount, command: res.command } | ||
| return origCb(err, res) | ||
| } | ||
| queryChannel.traceCallback( | ||
| (tracedCb) => { | ||
| query.callback = tracedCb | ||
| enqueue() |
There was a problem hiding this comment.
🟡 Timed-out queries leave traces unfinished
With query_timeout, the timer invokes queryCallback directly instead of the tracing wrapper. Timed-out queries never publish their trace error or completion.
Learn more
A query timeout is set up in the timeout handler before the new tracing wrapper replaces query.callback. The timer calls its saved queryCallback directly, then replaces query.callback with a noop. Neither the timeout nor a later query completion reaches the trace callback, so the pg:query operation remains open to diagnostics subscribers.
Example: With query_timeout: 10, a stalled SELECT pg_sleep(1) rejects after ten milliseconds but its pg:query subscriber receives only the start and end events, not the timeout error or async end.
Recommended fix: Route timeout completion through the tracing wrapper exactly once, and keep the later query callback suppressed to avoid duplicate completion. Test a traced query that times out.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const enrichedCb = (err, res) => { | ||
| if (res) context.result = { rowCount: res.rowCount, command: res.command } | ||
| return origCb(err, res) |
There was a problem hiding this comment.
🟡 Multiple statement result metadata disappears
When a SQL string returns several results, enrichedCb reads metadata from their array instead of each result. The trace reports undefined command and row count.
Learn more
The Query results become an array when one simple-query string contains multiple statements. Query.handleReadyForQuery passes this array as res. Arrays have no rowCount or command, so diagnostics subscribers receive undefined metadata for successful multi-statement queries.
Example: client.query('SELECT 1; SELECT 2') resolves with two results, but pg:query reports { rowCount: undefined, command: undefined }.
Recommended fix: Handle array results explicitly when building the tracing context, preserving per-statement command and row count, and cover multi-statement queries in the tests.
Was this helpful? React with 👍 or 👎 to provide feedback.
| this._pulseQueryQueue() | ||
| } | ||
|
|
||
| if (shouldTrace(queryChannel) && query.callback) { |
There was a problem hiding this comment.
| const defaults = require('./defaults') | ||
| const Connection = require('./connection') | ||
| const crypto = require('./crypto/utils') | ||
| const { queryChannel, connectionChannel, shouldTrace } = require('./diagnostics') |
There was a problem hiding this comment.
🔍 Native client has no matching traces
The native client has separate connect and query methods, so pg.native emits neither new lifecycle trace. Confirm the intended coverage.
Was this helpful? React with 👍 or 👎 to provide feedback.
| function shouldTrace(channel) { | ||
| return channel.hasSubscribers !== false | ||
| } |
There was a problem hiding this comment.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b922f0d. Configure here.
| @@ -1,5 +1,6 @@ | |||
| 'use strict' | |||
| const EventEmitter = require('events').EventEmitter | |||
| const { poolConnectChannel, poolReleaseChannel, poolRemoveChannel, shouldTrace } = require('./diagnostics') | |||
There was a problem hiding this comment.
Unpublished pool diagnostics module
High Severity
index.js now requires ./diagnostics, but that file is not in the pg-pool package files list (index.js and esm only). The published tarball omits it, so installing pg-pool from npm throws MODULE_NOT_FOUND and the pool cannot load.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b922f0d. Configure here.
| context, | ||
| null, | ||
| enrichedCb | ||
| ) |
There was a problem hiding this comment.
Query timeout leaves traces unfinished
Medium Severity
When query_timeout fires, the timeout handler calls the original callback and replaces query.callback with a no-op, so the traceCallback wrapper never runs. Subscribers get start but never asyncEnd or error, leaving APM spans open for the queries that most need them.
Reviewed by Cursor Bugbot for commit b922f0d. Configure here.


This PR introduces tracing channels support to
pg-poolandpgquerying methods as discussed in #3619I haven't yet run a benchmark, but without active tracing or subscribers the overhead is non-existent so existing users shouldn't be affected. The overhead would come from the consumers of the diag channels published here which is done anyways via monkey patching.
I added the following channels:
pg:querypgpg:connectionpgpg:pool:connectpg-poolpg:pool:releasepg-poolpg:pool:removepg-poolI understand there may be hesitation given that AI tooling was used in the implementation. To be clear, this isn't a drive-by contribution. I designed the channel layout, wrote the proposal document, and iterated through multiple rounds to handle real edge cases (Node 18 compat, preserving custom Promise types, hasSubscribers guards). The AI assisted with the code, but the approach and decisions are mine.
Usage
Instrumentations will only need to subscribe to tracing channels to create traces, logs or metrics:
This is part of a broader initiative to bring TracingChannel support to the most widely used Node.js database and cache libraries. The same pattern has already been merged and shipped in:
I have been directly involved in those prior implementations and I'm happy to own this through to release, address any feedback, and provide whatever support is needed. Would love to hear your thoughts.
Supersedes #3624.
Note
Medium Risk
Hooks wrap hot paths (
connect,query, pool checkout/release) via callback substitution; guards limit overhead when unsubscribed, but regressions in async/callback ordering are the main concern.Overview
Adds Node.js
diagnostics_channelinstrumentation topgandpg-poolso APM/tracing tools can subscribe without monkey-patching.pgexposespg:queryandpg:connectionTracingChannels.Client.connectand callback-basedClient.querypaths wrap work withtraceCallback, attaching connection metadata, query text/name/rowMode, and (on success)rowCount/commandon async completion.pg-pooladdspg:pool:connect(TracingChannel with pool counts and clientprocessID/ reuse flag), pluspg:pool:releaseandpg:pool:removepublish channels (including release errors).Shared
diagnostics.jshelpers load the built-in module when available, fall back to no-op channels otherwise, and useshouldTrace(hasSubscribers !== false) so Node 18 still traces when the aggregated getter is missing. Unit tests cover channel payloads and skip unstable TracingChannel behavior on older Node versions.Reviewed by Cursor Bugbot for commit b922f0d. Configure here.