Skip to content

Add diagnostics_channel support for query and connection lifecycle events #3619

Open

Description

@logaretm

I'd like to propose adding first-class TracingChannel support to pg and pg-pool, following the pattern established by undici in Node.js core.

TracingChannel is a higher-level API built on top of diagnostics_channel, specifically designed for tracing async operations. It provides structured lifecycle channels (start, end, error, asyncStart, asyncEnd) and handles async context propagation correctly, which was the missing piece that makes existing monkey-patching approaches fragile in real-world async Node.js applications.

Current instrumentation's today use IITM (import-in-the-middle) for ESM and RITM for CJS which has a few fragility concerns in today's ecosystem state, a few issues we have today:

  • Runtime lock-in: both 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 pg is first require()'d. Get the order wrong and instrumentation silently does nothing, which is very hard to debug in production.
  • Bundling and Externalization: Users have to ensure their instrumented modules are externalized, which is becoming very difficult to guarantee with more and more frameworks bundling the server-side code into single executables, binaries or deployment files.

If pg emits structured events through TracingChannel, instrumentation libraries become subscribers, not patches. Each tool listens independently with no ordering concern, no clobbering, and no internal API dependency.

Before putting together a concrete proposal and PR, wanted to check: is there appetite for this? Happy to spec out channel names, payload shapes, and Node version compatibility considerations if so.

Activity

  1. brianc commented on Mar 2, 2026

    @brianc
    Owner

    Definitely open to this! 👍 making instrumentation libraries not rely on monkey patching sounds wonderful.

  2. logaretm commented on Mar 2, 2026

    @logaretm
    Author

    Great to hear! this is what I was thinking:

    Channels

    We could have three TracingChannel instances, each providing start / end / asyncStart / asyncEnd / error:

    Channel Instruments Package
    pg:query Client.prototype.query pg
    pg:connection Client.prototype.connect pg
    pg:pool:connect Pool.prototype.connect pg-pool

    Plus plain channels using diagnostic_channels.channel for non-lifecycle events if needed:

    Channel When
    pg:pool:release Client returned to pool
    pg:pool:remove Client destroyed/evicted

    Payloads

    Message payloads can be something like this, I had AI sketch them out but we could add more stuff or remove them if needed:

    pg:query

    {
      // start
      query: { text: string; name?: string; rowMode?: string }
      client: { database: string; host: string; port: number; user: string; processID?: number; ssl: boolean }
    
      // asyncEnd
      result?: { rowCount: number | null; command: string }
    
      // error
      error?: Error
    }

    pg:connection

    {
      // start
      connection: { database: string; host: string; port: number; user: string; ssl: boolean | object }
    
      // asyncEnd
      serverVersion?: string
      processID?: number
    
      // error
      error?: Error
    }

    pg:pool:connect

    {
      // start
      pool: { totalCount: number; idleCount: number; waitingCount: number; maxSize: number }
    
      // asyncEnd
      client?: { processID?: number; reused: boolean }
    
      // error
      error?: Error
    }

    Instrumentation Pattern

    This is the cool part, providers/users or whoever wants to listen in to those tracing events can do something like this:

    const dc = require('node:diagnostics_channel');
    const queryChannel = dc.tracingChannel('pg:query');
    
    queryChannel.subscribe({
      start(paylaod) {
        // start spans or logging or whatever
      },
      asyncEnd(payload) {
        // end the span or report the result in a log or analytics...
      },
     error(err) {
       // handle thrown errors
     }
    })

    For implementation I can start looking into making a PR and go from there, what do you think?

  3. brianc commented on Mar 4, 2026

    @brianc
    Owner

    I think that all looks pretty good. Before you start - do you know what kind of performance overhead this would have? I assume allocating a single object for passing to the tracing chanel isn't a huge deal on each call, but interested in that. I can put more comments on the exact payload shapes here if you want, or I can just wait until there's a PR put up. One thing to note is pg currently supports all lts versions of node, so you might have to conditionally require or no-op stuff if it doesn't existin in older versions of node.

  4. logaretm commented on Mar 4, 2026

    @logaretm
    Author

    Yea there are a few strategies for addressing those:

    • haSubscribers property we can NO-OP if that's false and follow the regular codepath to avoid introducing overhead if there is no listeners, but generally tracing channels have very minimal idle overhead. It depends on what the listener is doing which in case of APM would be significant but wouldn't be a concern of pg itself I think. WDYT?
    • We can use process.getBuiltInModule if available to load in tracing channels, if they don't exist we can no-op. if getBuiltInModule doesn't exist we can also NO-OP since it likely won't have tracing channels either.

    I can draft up a PR and we can discuss the payloads and specifics there since I have still to check what's possible there.

  5. added a commit that references this issue on Mar 5, 2026
    b7f80ac
  6. sharon77242 commented on May 8, 2026

    @sharon77242
  7. added 2 commits that reference this issue on Jun 2, 2026
    0b5dff6
    790f05b
  8. added a commit that references this issue on Oct 9, 2026
    de87909
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions