Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions packages/server-utils/src/integrations/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface PgChannelContext extends OrchestrionChannelContext {
_sentryCallerScope?: Scope;
}

interface PgConnectionParams {
export interface PgConnectionParams {
database?: string;
host?: string;
port?: number;
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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,
Expand All @@ -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 } {
Expand All @@ -225,17 +242,21 @@ 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,
[SERVER_PORT]: Number.isInteger(params.port) ? params.port : undefined,
};
}

function getPgConnectionAttributes(params: PgConnectionParams): SpanAttributes {
return { ...getConnectionAttributes(params), [ATTR_DB_CONNECTION_STRING]: getConnectionString(params) };
}

function getPoolConnectionAttributes(opts: PgPoolOptions): SpanAttributes {
let url: URL | undefined;
try {
Expand Down
Loading