Hi, I work at Sentry and we've been leading an effort to help the ecosystem adopt tracing channels and given how popular this library is and that we already maintain an instrumentation for it, I thought I would reach out.
I'm suggesting adding a first-class TracingChannel support to Postgres.js, following the pattern established by undici in Node.js core.
TracingChannel is a higher-level API built on top of diagnostics_channel, designed for tracing async operations. It provides structured lifecycle channels (start, end, error, asyncStart, asyncEnd) and handles async context propagation correctly.
Motivation
Why native tracing matters for Postgres.js
Postgres.js is one of the most popular PostgreSQL clients for Node.js. APM tools (Sentry, OTel, Datadog) need to instrument it for query-level observability. Today they do this through monkey-patching, which is fragile and has several 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.
- Initialization ordering: Both require instrumentation to be set up before
postgres is first require()'d / import'd. Get the order wrong and instrumentation silently does nothing.
- Bundling: Users must ensure instrumented modules are externalized, which is increasingly difficult as frameworks bundle server-side code.
Given the alignment on having a runtime agnostic code, I think tracing channels is a great addition without compromising on that. Tracing channels are supported on Node, Bun, Deno and Cloudflare workers.
What APM tools do today
Sentry's current instrumentation (428 LOC) wraps the module's default export, proxies resolve/reject on each query to capture completion, and patches Query.prototype.handle across three file paths as a fallback for pre-existing instances. All of this breaks if Postgres.js renames internals or refactors the query flow.
Relationship to the existing debug option
Postgres.js already has a debug callback:
const sql = postgres({ debug: (id, string, parameters, types) => { ... } })
This fires during the build() phase and provides the query string and parameters. However, it doesn't cover the async lifecycle (no completion/error events), doesn't propagate async context, and requires per-instance configuration.
TracingChannels complements debug by providing structured lifecycle events that APM tools can subscribe to globally.
Proposed Tracing Channels
I'm thinking the following channels could be a good start for this proposal, covering most use-cases and operations.
| Channel |
Type |
Tracks |
postgres:query |
TracingChannel |
Query execution (tagged template sql...``, .execute()) |
postgres:connection |
TracingChannel |
Connection establishment |
postgres:transaction |
TracingChannel |
Transaction lifecycle (sql.begin()) |
Usage Example
If Tracing Channels were to be added to this library, an APM or even the user can do:
```js
const dc = require('node:diagnostics_channel');
dc.tracingChannel('postgres:query').subscribe({
start(ctx) {
ctx.span = tracer.startSpan(ctx.result?.command || 'postgres.query', {
attributes: {
'db.system': 'postgres',
'db.query.text': sanitize(ctx.query.text),
'db.namespace': ctx.connection.database,
'server.address': ctx.connection.host,
'server.port': ctx.connection.port,
},
});
},
asyncEnd(ctx) {
if (ctx.result) {
ctx.span?.setAttribute('db.operation.name', ctx.result.command);
}
ctx.span?.end();
},
error(ctx) {
ctx.span?.setStatus({ code: SpanStatusCode.ERROR, message: ctx.error?.message });
ctx.span?.end();
},
});
dc.tracingChannel('postgres:transaction').subscribe({
start(ctx) {
ctx.span = tracer.startSpan('postgres.transaction');
},
asyncEnd(ctx) {
ctx.span?.setAttribute('db.transaction.committed', ctx.result?.committed);
ctx.span?.end();
},
error(ctx) {
ctx.span?.setStatus({ code: SpanStatusCode.ERROR });
ctx.span?.end();
},
});
This would work on all server runtimes, without need for module patching or monkey patching.
Prior Art
This approach follows the same pattern already adopted by other database libraries:
among many others.
Happy to put together a PR with the implementation.
Hi, I work at Sentry and we've been leading an effort to help the ecosystem adopt tracing channels and given how popular this library is and that we already maintain an instrumentation for it, I thought I would reach out.
I'm suggesting adding a first-class
TracingChannelsupport to Postgres.js, following the pattern established byundiciin Node.js core.TracingChannelis a higher-level API built on top ofdiagnostics_channel, designed for tracing async operations. It provides structured lifecycle channels (start,end,error,asyncStart,asyncEnd) and handles async context propagation correctly.Motivation
Why native tracing matters for Postgres.js
Postgres.js is one of the most popular PostgreSQL clients for Node.js. APM tools (Sentry, OTel, Datadog) need to instrument it for query-level observability. Today they do this through monkey-patching, which is fragile and has several ecosystem concerns:
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.postgresis firstrequire()'d /import'd. Get the order wrong and instrumentation silently does nothing.Given the alignment on having a runtime agnostic code, I think tracing channels is a great addition without compromising on that. Tracing channels are supported on Node, Bun, Deno and Cloudflare workers.
What APM tools do today
Sentry's current instrumentation (428 LOC) wraps the module's default export, proxies
resolve/rejecton each query to capture completion, and patchesQuery.prototype.handleacross three file paths as a fallback for pre-existing instances. All of this breaks if Postgres.js renames internals or refactors the query flow.Relationship to the existing
debugoptionPostgres.js already has a
debugcallback:This fires during the
build()phase and provides the query string and parameters. However, it doesn't cover the async lifecycle (no completion/error events), doesn't propagate async context, and requires per-instance configuration.TracingChannels complements
debugby providing structured lifecycle events that APM tools can subscribe to globally.Proposed Tracing Channels
I'm thinking the following channels could be a good start for this proposal, covering most use-cases and operations.
postgres:querysql...``,.execute())postgres:connectionpostgres:transactionsql.begin())Usage Example
If Tracing Channels were to be added to this library, an APM or even the user can do:
This would work on all server runtimes, without need for module patching or monkey patching.
Prior Art
This approach follows the same pattern already adopted by other database libraries:
among many others.
Happy to put together a PR with the implementation.