Skip to content

Add diagnostics_channel TracingChannel support#3624

Open
logaretm wants to merge 3 commits intobrianc:masterfrom
logaretm:worktree-proud-squishing-wreath
Open

Add diagnostics_channel TracingChannel support#3624
logaretm wants to merge 3 commits intobrianc:masterfrom
logaretm:worktree-proud-squishing-wreath

Conversation

@logaretm
Copy link

@logaretm logaretm commented Mar 5, 2026

This PR introduces tracing channels support to pg-pool and pg querying methods as discussed in #3619

I 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 planned a draft document and had claude do the implementation, I adjust the approach a few times to ensure we don't introduce a lot of code and redundancies.

Summary

  • Adds Node.js diagnostics_channel TracingChannel support to pg and pg-pool, enabling instrumentation libraries (OpenTelemetry, etc.) to subscribe to structured events without monkey-patching
  • Introduces 5 channels: pg:query, pg:connection (TracingChannels with full async lifecycle), pg:pool:connect (TracingChannel), pg:pool:release, and pg:pool:remove (plain channels)
  • All instrumentation guarded by hasSubscribers — zero overhead when no subscribers are attached
  • Gracefully degrades to no-ops on Node < 19.9 or non-Node environments that doesn't support it

Closes #3619

🤖 Generated with Claude Code

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 brianc#3619

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 5, 2026 17:40
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>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds diagnostics_channel TracingChannel instrumentation for pg and pg-pool so observability tooling can subscribe to structured lifecycle events without monkey-patching, with zero overhead when no subscribers exist and graceful degradation when unsupported.

Changes:

  • Introduces diagnostics channel loaders for pg and pg-pool (TracingChannels + plain channels where appropriate)
  • Instruments pg client connect + query lifecycle and pg-pool connect/release/remove events
  • Adds new unit tests validating published diagnostics events and context enrichment

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/pg/lib/diagnostics.js Adds lazy/guarded creation of pg:query and pg:connection tracing channels
packages/pg/lib/client.js Instruments connect() and query enqueue/completion with diagnostics tracing
packages/pg/test/unit/client/diagnostics-tests.js Adds unit tests for pg query + connection diagnostics events
packages/pg-pool/diagnostics.js Adds lazy/guarded creation of pool connect tracing channel and release/remove channels
packages/pg-pool/index.js Publishes pool remove/release events and traces pool connect lifecycle
packages/pg-pool/test/diagnostics.js Adds unit tests for pg-pool diagnostics channels

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +36
const subs = {
start: (ctx) => events.push({ type: 'start', context: ctx }),
end: () => {},
asyncStart: () => {},
asyncEnd: (ctx) => {
events.push({ type: 'asyncEnd', context: ctx })

// asyncEnd fires after the callback, so check everything here
assert.equal(events.length, 2)
assert.equal(events[0].type, 'start')
assert.equal(events[0].context.query.text, 'SELECT 1')
assert.equal(events[0].context.client.database, client.database)

assert.equal(events[1].type, 'asyncEnd')
assert.equal(events[1].context.result.command, 'SELECT')
assert.equal(events[1].context.result.rowCount, 1)
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsubscribing is done only at the end of the success path inside callbacks (e.g., asyncEnd). If an assertion throws before unsubscribe (or the test times out), the subscription can leak into later tests and cause cross-test interference. Prefer ensuring channel.unsubscribe(subs) runs via a try/finally around assertions or a suite-level teardown/afterEach hook.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +26
let poolConnectChannel = noopChannel
let poolReleaseChannel = noopChannel
let poolRemoveChannel = noopChannel

try {
let dc
if (typeof process.getBuiltInModule === 'function') {
dc = process.getBuiltInModule('diagnostics_channel')
} else {
dc = require('diagnostics_channel')
}
if (typeof dc.tracingChannel === 'function') {
poolConnectChannel = dc.tracingChannel('pg:pool:connect')
}
if (typeof dc.channel === 'function') {
poolReleaseChannel = dc.channel('pg:pool:release')
poolRemoveChannel = dc.channel('pg:pool:remove')
}
} catch (e) {
// diagnostics_channel not available (non-Node environment)
}

Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnostics bootstrap logic here is very similar to packages/pg/lib/diagnostics.js (built-in-module fallback, feature detection, noop channels). To reduce duplication and avoid the two implementations drifting over time, consider extracting a small shared helper (even internal) for: loading diagnostics_channel safely, creating tracing channels, and creating plain channels.

Suggested change
let poolConnectChannel = noopChannel
let poolReleaseChannel = noopChannel
let poolRemoveChannel = noopChannel
try {
let dc
if (typeof process.getBuiltInModule === 'function') {
dc = process.getBuiltInModule('diagnostics_channel')
} else {
dc = require('diagnostics_channel')
}
if (typeof dc.tracingChannel === 'function') {
poolConnectChannel = dc.tracingChannel('pg:pool:connect')
}
if (typeof dc.channel === 'function') {
poolReleaseChannel = dc.channel('pg:pool:release')
poolRemoveChannel = dc.channel('pg:pool:remove')
}
} catch (e) {
// diagnostics_channel not available (non-Node environment)
}
function loadDiagnosticsChannel() {
try {
if (typeof process.getBuiltInModule === 'function') {
return process.getBuiltInModule('diagnostics_channel')
}
return require('diagnostics_channel')
} catch (e) {
// diagnostics_channel not available (non-Node environment)
return null
}
}
function createTracingChannel(dc, name) {
if (dc && typeof dc.tracingChannel === 'function') {
return dc.tracingChannel(name)
}
return noopChannel
}
function createChannel(dc, name) {
if (dc && typeof dc.channel === 'function') {
return dc.channel(name)
}
return noopChannel
}
const diagnosticsChannel = loadDiagnosticsChannel()
const poolConnectChannel = createTracingChannel(diagnosticsChannel, 'pg:pool:connect')
const poolReleaseChannel = createChannel(diagnosticsChannel, 'pg:pool:release')
const poolRemoveChannel = createChannel(diagnosticsChannel, 'pg:pool:remove')

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add diagnostics_channel support for query and connection lifecycle events

2 participants