From c3f20dbe45550ca255666bc862c4bbf614df6d1c Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 8 Oct 2026 11:11:40 +0200 Subject: [PATCH] ref(server-utils): Export the pg query span helpers Expose the query-channel binding and the connection attribute builder so a driver that bundles its own copy of pg can emit the same span shape. The deprecated db.connection_string attribute moves behind an opt-in that only the pg integration sets, so new callers describe the connection with server.address and server.port alone. No behavior change for pg. Refs #25144 Co-Authored-By: Claude Fable 5.1 --- .../server-utils/src/integrations/postgres.ts | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/postgres.ts index c1a2980943db..9dc4e62bf01e 100644 --- a/packages/server-utils/src/integrations/postgres.ts +++ b/packages/server-utils/src/integrations/postgres.ts @@ -65,7 +65,7 @@ interface PgChannelContext extends OrchestrionChannelContext { _sentryCallerScope?: Scope; } -interface PgConnectionParams { +export interface PgConnectionParams { database?: string; host?: string; port?: number; @@ -92,9 +92,7 @@ const _postgresIntegration = ((options: { ignoreConnectSpans?: boolean } = {}) = }) satisfies IntegrationFn; function instrumentPostgres(options: { ignoreConnectSpans?: boolean }): void { - // Query spans: `pg`/native `Client.prototype.query`. Only this channel can return a streamable - // `Submittable` result, so it's the only one that defers span-ending to the emitter (see below). - subscribeQueryLikeChannel(CHANNELS.PG_QUERY, querySpanOptions, { deferStreamedResult: true }); + bindPgQueryChannel(CHANNELS.PG_QUERY, { origin: ORIGIN, connectionString: true }); // Connect spans, gated by `ignoreConnectSpans` (same as OTel pg). // `Client.prototype.connect` (pg + native) @@ -105,6 +103,22 @@ function instrumentPostgres(options: { ignoreConnectSpans?: boolean }): void { } } +export interface PgQuerySpanOptions { + origin: string; + // `db.connection_string` is deprecated in favor of `server.address`/`server.port`. Only the pg + // integration keeps emitting it, for parity with its OTel predecessor. + connectionString?: boolean; +} + +/** + * Query spans for a channel carrying `pg` `Client.prototype.query` calls, from `pg` itself or from + * a copy bundled into another driver (`@neondatabase/serverless`). Only this call can return a + * streamable `Submittable` result, so it's the only one that defers span-ending to the emitter. + */ +export function bindPgQueryChannel(channelName: string, options: PgQuerySpanOptions): void { + subscribeQueryLikeChannel(channelName, ctx => querySpanOptions(ctx, options), { deferStreamedResult: true }); +} + /** * Subscribe to a pg tracing-channel and manage a span across its lifecycle. * Shared by the query/connect/pool-connect channels. They differ only in how @@ -172,7 +186,10 @@ function subscribeQueryLikeChannel( ); } -function querySpanOptions(ctx: PgChannelContext): { name: string; attributes: SpanAttributes } { +function querySpanOptions( + ctx: PgChannelContext, + { origin, connectionString }: PgQuerySpanOptions, +): { name: string; attributes: SpanAttributes } { const params = (ctx.self as { connectionParameters?: PgConnectionParams } | undefined)?.connectionParameters ?? {}; const queryConfig = extractQueryConfig(ctx.arguments); const client = getClient(); @@ -186,8 +203,8 @@ function querySpanOptions(ctx: PgChannelContext): { name: string; attributes: Sp name, attributes: { [SENTRY_OP]: DB, - ...getConnectionAttributes(params), - [SENTRY_ORIGIN]: ORIGIN, + ...(connectionString ? getPgConnectionAttributes(params) : getConnectionAttributes(params)), + [SENTRY_ORIGIN]: origin, [DB_QUERY_TEXT]: queryText || undefined, [DB_QUERY_SUMMARY]: querySummary, [ATTR_PG_PLAN]: typeof queryConfig?.name === 'string' ? queryConfig.name : undefined, @@ -198,7 +215,7 @@ function querySpanOptions(ctx: PgChannelContext): { name: string; attributes: Sp function connectSpanOptions(ctx: PgChannelContext): { name: string; attributes: SpanAttributes } { const params = (ctx.self as { connectionParameters?: PgConnectionParams } | undefined)?.connectionParameters ?? {}; // No origin set -> defaults to 'manual' - return { name: SPAN_CONNECT, attributes: { [SENTRY_OP]: DB, ...getConnectionAttributes(params) } }; + return { name: SPAN_CONNECT, attributes: { [SENTRY_OP]: DB, ...getPgConnectionAttributes(params) } }; } function poolConnectSpanOptions(ctx: PgChannelContext): { name: string; attributes: SpanAttributes } { @@ -225,10 +242,10 @@ function extractQueryConfig(args: unknown[]): { text: string; name?: unknown } | return undefined; } -function getConnectionAttributes(params: PgConnectionParams): SpanAttributes { +/** The `db.*`/`server.*` attributes for a pg-style set of connection parameters. */ +export function getConnectionAttributes(params: PgConnectionParams): SpanAttributes { return { [DB_SYSTEM_NAME]: DB_SYSTEM_POSTGRESQL, - [ATTR_DB_CONNECTION_STRING]: getConnectionString(params), [DB_NAMESPACE]: params.database, [DB_USER]: params.user, [SERVER_ADDRESS]: params.host, @@ -236,6 +253,10 @@ function getConnectionAttributes(params: PgConnectionParams): SpanAttributes { }; } +function getPgConnectionAttributes(params: PgConnectionParams): SpanAttributes { + return { ...getConnectionAttributes(params), [ATTR_DB_CONNECTION_STRING]: getConnectionString(params) }; +} + function getPoolConnectionAttributes(opts: PgPoolOptions): SpanAttributes { let url: URL | undefined; try {