From 306269f99168f04964dcf0e12d97c6353be03bc3 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 19 Jun 2025 15:59:20 +0100 Subject: [PATCH 01/11] feat(node): Add postgresjs instrumentation --- .../node-integration-tests/package.json | 1 + .../tracing/postgresjs/docker-compose.yml | 13 + .../suites/tracing/postgresjs/scenario.js | 61 +++++ .../suites/tracing/postgresjs/test.ts | 198 ++++++++++++++ .../node/src/integrations/tracing/index.ts | 3 + .../src/integrations/tracing/postgresjs.ts | 257 ++++++++++++++++++ yarn.lock | 5 + 7 files changed, 538 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts create mode 100644 packages/node/src/integrations/tracing/postgresjs.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 800a3602677f..45b10e43199a 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -61,6 +61,7 @@ "node-cron": "^3.0.3", "node-schedule": "^2.1.1", "pg": "8.16.0", + "postgres": "^3.4.7", "proxy": "^2.1.1", "redis-4": "npm:redis@^4.6.14", "reflect-metadata": "0.2.1", diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/postgresjs/docker-compose.yml new file mode 100644 index 000000000000..301280106faa --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-postgresjs + ports: + - '5444:5432' + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test_db diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js b/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js new file mode 100644 index 000000000000..8c4281cd821b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js @@ -0,0 +1,61 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const postgres = require('postgres'); + +const sql = postgres({ port: 5444, user: 'test', password: 'test', database: 'test_db' }); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await sql` + CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id")); + `; + + await sql` + INSERT INTO "User" ("email", "name") VALUES ('Foo', 'bar@baz.com'); + `; + + await sql` + UPDATE "User" SET "name" = 'Foo' WHERE "email" = 'bar@baz.com'; + `; + + await sql` + SELECT * FROM "User" WHERE "email" = 'bar@baz.com'; + `; + + await sql`SELECT * from generate_series(1,1000) as x `.cursor(10, async rows => { + await Promise.all(rows); + }); + + await sql` + DROP TABLE "User"; + `; + + await sql` + SELECT * FROM "User" WHERE "email" = 'bar@baz.com'; + `; + } finally { + await sql.end(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts new file mode 100644 index 000000000000..6633f0c72383 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -0,0 +1,198 @@ +import { describe, expect, test } from 'vitest'; +import { createRunner } from '../../../utils/runner'; + +describe('postgresjs auto instrumentation', () => { + test('should auto-instrument `postgres` package', { timeout: 60_000 }, async () => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'CREATE TABLE', + 'db.query.text': + 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'CREATE TABLE db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'SELECT', + 'db.query.text': + "select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid", + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'SELECT db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'INSERT', + 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', + 'db.system.name': 'postgres', + 'sentry.origin': 'manual', + 'sentry.op': 'db', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'INSERT db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'UPDATE', + 'db.query.text': 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'UPDATE db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'SELECT', + 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'SELECT db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'SELECT', + 'db.query.text': 'SELECT * from generate_series(?,?) as x', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'SELECT db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.operation.name': 'DROP TABLE', + 'db.query.text': 'DROP TABLE "User"', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'DROP TABLE db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.namespace': 'test_db', + 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', + 'db.system.name': 'postgres', + 'sentry.op': 'db', + 'sentry.origin': 'manual', + 'server.address': 'localhost', + 'server.port': 5444, + }), + description: 'SELECT db:test_db', + op: 'db', + status: 'ok', + origin: 'manual', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }), + ]), + }; + + const EXPECTED_ERROR_EVENT = { + event_id: expect.any(String), + contexts: { + trace: { + trace_id: expect.any(String), + span_id: expect.any(String), + status: 'unknown_error', + }, + }, + }; + + await createRunner(__dirname, 'scenario.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] }) + .expect({ transaction: EXPECTED_TRANSACTION, event: EXPECTED_ERROR_EVENT }) + .start() + .completed(); + }); +}); diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 425710cae0ce..e7122562d619 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -15,6 +15,7 @@ import { instrumentMongoose, mongooseIntegration } from './mongoose'; import { instrumentMysql, mysqlIntegration } from './mysql'; import { instrumentMysql2, mysql2Integration } from './mysql2'; import { instrumentPostgres, postgresIntegration } from './postgres'; +import { instrumentPostgresJs, postgresJsIntegration } from './postgresjs'; import { prismaIntegration } from './prisma'; import { instrumentRedis, redisIntegration } from './redis'; import { instrumentTedious, tediousIntegration } from './tedious'; @@ -44,6 +45,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { amqplibIntegration(), lruMemoizerIntegration(), vercelAIIntegration(), + postgresJsIntegration(), ]; } @@ -75,5 +77,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentGenericPool, instrumentAmqplib, instrumentVercelAi, + instrumentPostgresJs, ]; } diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts new file mode 100644 index 000000000000..eeb29ee3d8bd --- /dev/null +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -0,0 +1,257 @@ +// Instrumentation for https://github.com/porsager/postgres +import { SpanStatusCode } from '@opentelemetry/api'; +import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, +} from '@opentelemetry/instrumentation'; +import { + ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_SERVER_ADDRESS, + ATTR_SERVER_PORT, +} from '@opentelemetry/semantic-conventions'; +import type { IntegrationFn, Span } from '@sentry/core'; +import { defineIntegration, getCurrentScope, startSpanManual } from '@sentry/core'; +import { SDK_VERSION } from '../..'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const INTEGRATION_NAME = 'PostgresJs'; +const SUPPORTED_VERSIONS = ['>=3.0.0 <4']; + +export const instrumentPostgresJs = generateInstrumentOnce(INTEGRATION_NAME, () => new PostgresJsInstrumentation()); + +/** + * Instrumentation for the [postgres](https://www.npmjs.com/package/postgres) library. + * This instrumentation captures postgresjs queries and their attributes, + */ +export class PostgresJsInstrumentation extends InstrumentationBase { + public constructor(config: InstrumentationConfig = {}) { + super('sentry-postgres-js', SDK_VERSION, config); + } + + /** + * Initializes the instrumentation. + */ + public init(): InstrumentationNodeModuleDefinition[] { + const instrumentationModule = new InstrumentationNodeModuleDefinition('postgres', SUPPORTED_VERSIONS); + + ['src', 'cf/src', 'cjs/src'].forEach(path => { + instrumentationModule.files.push( + new InstrumentationNodeModuleFile( + `postgres/${path}/connection.js`, + ['*'], + this._patchConnection.bind(this), + this._unwrap.bind(this), + ), + ); + + instrumentationModule.files.push( + new InstrumentationNodeModuleFile( + `postgres/${path}/query.js`, + SUPPORTED_VERSIONS, + this._patchQuery.bind(this), + this._unwrap.bind(this), + ), + ); + }); + + return [instrumentationModule]; + } + + /** + * + */ + private _patchReject(rejectTarget: any, span: Span): any { + return new Proxy(rejectTarget, { + apply: ( + rejectTarget, + rejectThisArg, + rejectArgs: { + message?: string; + }[], + ) => { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: rejectArgs[0]?.message || 'PostgresJs Query Error', + }); + + const result = Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); + span.end(); + return result; + }, + }); + } + + /** + * Patches the resolve method of the Query class to start a span + * and set the relevant attributes. + */ + private _patchResolve(resolveTarget: any, span: Span, databaseName: string): any { + return new Proxy(resolveTarget, { + apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string; statement?: { string: string } }]) => { + const sqlCommand = resolveArgs?.[0]?.command; + + const spanDescription = sqlCommand + ? `${sqlCommand.replace(/\s+/g, ' ').trim()} db:${databaseName}` + : `Unknown Query db:${databaseName}`; + + span.updateName(spanDescription); + + // Set command and database attributes on the span + span.setAttribute(ATTR_DB_OPERATION_NAME, sqlCommand); + + const result = Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); + span.end(); + return result; + }, + }); + } + + /** + * + */ + private _patchQuery(moduleExports: { + Query: { + prototype: { + handle: any; + }; + }; + }): any { + moduleExports.Query.prototype.handle = new Proxy(moduleExports.Query.prototype.handle, { + apply: async ( + handleTarget, + handleThisArg: { + resolve: any; + reject: any; + resolveArgs: [{ command?: string; statement?: { string: string } }]; + strings?: string[]; + }, + handleArgs, + ) => { + return startSpanManual( + { + name: 'postgresjs.query', + op: 'db', + }, + (span: Span) => { + const scope = getCurrentScope(); + const postgresConnectionContext = scope.getScopeData().contexts['postgresjsConnection'] as + | { + ATTR_DB_NAMESPACE: string; + ATTR_SERVER_ADDRESS: string; + ATTR_SERVER_PORT: string; + } + | undefined; + + // ATTR_DB_NAMESPACE is used to indicate the database name and the schema name + // It's only the database name as we don't have the schema information + const databaseName = postgresConnectionContext?.ATTR_DB_NAMESPACE || ''; + const databaseHost = postgresConnectionContext?.ATTR_SERVER_ADDRESS || ''; + const databasePort = postgresConnectionContext?.ATTR_SERVER_PORT || ''; + const sanitizedSqlQuery = this._sanitizeSqlQuery(handleThisArg.strings?.[0]); + + span.setAttribute(ATTR_DB_SYSTEM_NAME, 'postgres'); + span.setAttribute(ATTR_DB_NAMESPACE, databaseName); + span.setAttribute(ATTR_SERVER_ADDRESS, databaseHost); + span.setAttribute(ATTR_SERVER_PORT, databasePort); + span.setAttribute(ATTR_DB_QUERY_TEXT, sanitizedSqlQuery); + + handleThisArg.resolve = this._patchResolve(handleThisArg.resolve, span, databaseName); + handleThisArg.reject = this._patchReject(handleThisArg.reject, span); + + try { + return Reflect.apply(handleTarget, handleThisArg, handleArgs); + } catch (error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: (error as Error).message || 'PostgresJs Query Error', + }); + span.end(); + throw error; // Re-throw the error to propagate it + } + }, + ); + }, + }); + + return moduleExports; + } + + /** + * + */ + private _patchConnection(Connection: any): any { + return new Proxy(Connection, { + apply: (connectionTarget, thisArg, connectionArgs: { database: string; host: string[]; port: number[] }[]) => { + const databaseName = connectionArgs[0]?.database || ''; + const databaseHost = connectionArgs[0]?.host?.[0] || ''; + const databasePort = connectionArgs[0]?.port?.[0] || ''; + + const scope = getCurrentScope(); + scope.setContext('postgresjsConnection', { + ATTR_DB_NAMESPACE: databaseName, + ATTR_SERVER_ADDRESS: databaseHost, + ATTR_SERVER_PORT: databasePort, + }); + + return Reflect.apply(connectionTarget, thisArg, connectionArgs); + }, + }); + } + + /** + * Sanitize SQL query as per the OTEL semantic conventions + * https://opentelemetry.io/docs/specs/semconv/database/database-spans/#sanitization-of-dbquerytext + */ + private _sanitizeSqlQuery(sqlQuery: string | undefined): string { + if (!sqlQuery) { + return 'Unknown SQL Query'; + } + + return ( + sqlQuery + .replace(/\s+/g, ' ') + .trim() // Remove extra spaces including newlines and trim + .substring(0, 1024) // Truncate to 1024 characters + .replace(/--.*?(\r?\n|$)/g, '') // Single line comments + .replace(/\/\*[\s\S]*?\*\//g, '') // Multi-line comments + .replace(/;\s*$/, '') // Remove trailing semicolons + .replace(/\b\d+\b/g, '?') // Replace standalone numbers + // Collapse whitespace to a single space + .replace(/\s+/g, ' ') + // Collapse IN and in clauses + // eg. IN (?, ?, ?, ?) to IN (?) + .replace(/\bIN\b\s*\((?:\s*\?\s*,?)+\)/g, 'IN (?)') + ); + } +} + +const _postgresJsIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentPostgresJs(); + }, + }; +}) satisfies IntegrationFn; + +/** + * Adds Sentry tracing instrumentation for the [postgres](https://www.npmjs.com/package/postgres) library. + * + * For more information, see the [`postgresIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/postgres/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.postgresJsIntegration()], + * }); + * ``` + */ + +export const postgresJsIntegration = defineIntegration(_postgresJsIntegration); diff --git a/yarn.lock b/yarn.lock index 1e714f6340be..839ac64b50d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24093,6 +24093,11 @@ postgres-range@^1.1.1: resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== +postgres@^3.4.7: + version "3.4.7" + resolved "https://registry.yarnpkg.com/postgres/-/postgres-3.4.7.tgz#122f460a808fe300cae53f592108b9906e625345" + integrity sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw== + preact@^10.19.4: version "10.19.4" resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.4.tgz#735d331d5b1bd2182cc36f2ba481fd6f0da3fe3b" From 34fa611a8c0edaf49de112c76d3a7be757dbbf69 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 20 Jun 2025 14:31:16 +0100 Subject: [PATCH 02/11] Potential fix for code scanning alert no. 400: Inefficient regular expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- packages/node/src/integrations/tracing/postgresjs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index eeb29ee3d8bd..32837de56cc2 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -225,7 +225,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase { .replace(/\s+/g, ' ') // Collapse IN and in clauses // eg. IN (?, ?, ?, ?) to IN (?) - .replace(/\bIN\b\s*\((?:\s*\?\s*,?)+\)/g, 'IN (?)') + .replace(/\bIN\b\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/g, 'IN (?)') ); } } From d7c93ea58ee93f3f6830a319008cbf87c76b382b Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 20 Jun 2025 14:37:13 +0100 Subject: [PATCH 03/11] Fix circular dependency --- packages/node/src/integrations/tracing/postgresjs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index 32837de56cc2..b2f38030d37d 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -16,7 +16,7 @@ import { } from '@opentelemetry/semantic-conventions'; import type { IntegrationFn, Span } from '@sentry/core'; import { defineIntegration, getCurrentScope, startSpanManual } from '@sentry/core'; -import { SDK_VERSION } from '../..'; +import { SDK_VERSION } from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; const INTEGRATION_NAME = 'PostgresJs'; From deb6a9ee95dd0706265e75833e8c6d72164fc198 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 20 Jun 2025 14:57:41 +0100 Subject: [PATCH 04/11] Add missing comments --- packages/node/src/integrations/tracing/postgresjs.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index b2f38030d37d..52d4930f2b99 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -63,7 +63,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase { } /** - * + * Patches the reject method of the Query class to set the span status */ private _patchReject(rejectTarget: any, span: Span): any { return new Proxy(rejectTarget, { @@ -112,7 +112,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase { } /** - * + * Patches the Query class to instrument the handle method. */ private _patchQuery(moduleExports: { Query: { @@ -182,7 +182,8 @@ export class PostgresJsInstrumentation extends InstrumentationBase { } /** - * + * Patches the Connection class to set the database, host, and port attributes + * when a new connection is created. */ private _patchConnection(Connection: any): any { return new Proxy(Connection, { From 3ba19f808d4014cdc3d69a28357479902b512009 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 20 Jun 2025 20:58:55 +0100 Subject: [PATCH 05/11] Update tests --- .../suites/tracing/postgresjs/scenario.js | 3 +- .../suites/tracing/postgresjs/test.ts | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js b/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js index 8c4281cd821b..e7cb92aabf27 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js @@ -47,8 +47,9 @@ async function run() { DROP TABLE "User"; `; + // This will be captured as an error as the table no longer exists await sql` - SELECT * FROM "User" WHERE "email" = 'bar@baz.com'; + SELECT * FROM "User" WHERE "email" = 'foo@baz.com'; `; } finally { await sql.end(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index 6633f0c72383..f492096596d3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -158,16 +158,18 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', + 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT db:test_db', + // This span is an error span and the `command` is not available when a does not resolve + // That's why we can't update the span description when the query fails + description: 'postgresjs.query', op: 'db', - status: 'ok', + status: 'unknown_error', origin: 'manual', parent_span_id: expect.any(String), span_id: expect.any(String), @@ -184,14 +186,33 @@ describe('postgresjs auto instrumentation', () => { trace: { trace_id: expect.any(String), span_id: expect.any(String), - status: 'unknown_error', }, }, + exception: { + values: [ + { + type: 'PostgresError', + value: 'relation "User" does not exist', + stacktrace: expect.objectContaining({ + frames: expect.arrayContaining([ + expect.objectContaining({ + function: 'handle', + module: 'postgres.cjs.src:connection', + filename: expect.any(String), + lineno: expect.any(Number), + colno: expect.any(Number), + }), + ]), + }), + }, + ], + }, }; await createRunner(__dirname, 'scenario.js') .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] }) - .expect({ transaction: EXPECTED_TRANSACTION, event: EXPECTED_ERROR_EVENT }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .expect({ event: EXPECTED_ERROR_EVENT }) .start() .completed(); }); From ff9248eb13afa68b670f3bc05257a059469292bb Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 23 Jun 2025 23:42:26 +0100 Subject: [PATCH 06/11] Clean up --- .../suites/tracing/postgresjs/test.ts | 37 +++++------------ .../src/integrations/tracing/postgresjs.ts | 41 +++++-------------- yarn.lock | 34 +++++++-------- 3 files changed, 38 insertions(+), 74 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index f492096596d3..deb460eb5e63 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -9,16 +9,14 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'CREATE TABLE', - 'db.query.text': - 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'CREATE TABLE db:test_db', + description: + 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', op: 'db', status: 'ok', origin: 'manual', @@ -31,16 +29,14 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'SELECT', - 'db.query.text': - "select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid", 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT db:test_db', + description: + "select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid", op: 'db', status: 'ok', origin: 'manual', @@ -53,15 +49,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', 'db.system.name': 'postgres', 'sentry.origin': 'manual', 'sentry.op': 'db', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'INSERT db:test_db', + description: 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', op: 'db', status: 'ok', origin: 'manual', @@ -74,15 +68,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'UPDATE', - 'db.query.text': 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'UPDATE db:test_db', + description: 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', op: 'db', status: 'ok', origin: 'manual', @@ -95,15 +87,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT db:test_db', + description: 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', op: 'db', status: 'ok', origin: 'manual', @@ -116,15 +106,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * from generate_series(?,?) as x', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT db:test_db', + description: 'SELECT * from generate_series(?,?) as x', op: 'db', status: 'ok', origin: 'manual', @@ -137,15 +125,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.operation.name': 'DROP TABLE', - 'db.query.text': 'DROP TABLE "User"', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'DROP TABLE db:test_db', + description: 'DROP TABLE "User"', op: 'db', status: 'ok', origin: 'manual', @@ -158,16 +144,13 @@ describe('postgresjs auto instrumentation', () => { expect.objectContaining({ data: expect.objectContaining({ 'db.namespace': 'test_db', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', 'db.system.name': 'postgres', 'sentry.op': 'db', 'sentry.origin': 'manual', 'server.address': 'localhost', 'server.port': 5444, }), - // This span is an error span and the `command` is not available when a does not resolve - // That's why we can't update the span description when the query fails - description: 'postgresjs.query', + description: 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', op: 'db', status: 'unknown_error', origin: 'manual', diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index 52d4930f2b99..efc64672bc0d 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -1,5 +1,4 @@ // Instrumentation for https://github.com/porsager/postgres -import { SpanStatusCode } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, @@ -8,15 +7,12 @@ import { } from '@opentelemetry/instrumentation'; import { ATTR_DB_NAMESPACE, - ATTR_DB_OPERATION_NAME, - ATTR_DB_QUERY_TEXT, ATTR_DB_SYSTEM_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, } from '@opentelemetry/semantic-conventions'; import type { IntegrationFn, Span } from '@sentry/core'; -import { defineIntegration, getCurrentScope, startSpanManual } from '@sentry/core'; -import { SDK_VERSION } from '@sentry/core'; +import { defineIntegration, getCurrentScope, SDK_VERSION, SPAN_STATUS_ERROR, startSpanManual } from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; const INTEGRATION_NAME = 'PostgresJs'; @@ -63,7 +59,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase { } /** - * Patches the reject method of the Query class to set the span status + * Patches the reject method of the Query class to set the span status and end it */ private _patchReject(rejectTarget: any, span: Span): any { return new Proxy(rejectTarget, { @@ -75,8 +71,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase { }[], ) => { span.setStatus({ - code: SpanStatusCode.ERROR, - message: rejectArgs[0]?.message || 'PostgresJs Query Error', + code: SPAN_STATUS_ERROR, }); const result = Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); @@ -87,23 +82,11 @@ export class PostgresJsInstrumentation extends InstrumentationBase { } /** - * Patches the resolve method of the Query class to start a span - * and set the relevant attributes. + * Patches the resolve method of the Query class to end the span when the query is resolved. */ - private _patchResolve(resolveTarget: any, span: Span, databaseName: string): any { + private _patchResolve(resolveTarget: any, span: Span): any { return new Proxy(resolveTarget, { - apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string; statement?: { string: string } }]) => { - const sqlCommand = resolveArgs?.[0]?.command; - - const spanDescription = sqlCommand - ? `${sqlCommand.replace(/\s+/g, ' ').trim()} db:${databaseName}` - : `Unknown Query db:${databaseName}`; - - span.updateName(spanDescription); - - // Set command and database attributes on the span - span.setAttribute(ATTR_DB_OPERATION_NAME, sqlCommand); - + apply: (resolveTarget, resolveThisArg, resolveArgs) => { const result = Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); span.end(); return result; @@ -127,14 +110,15 @@ export class PostgresJsInstrumentation extends InstrumentationBase { handleThisArg: { resolve: any; reject: any; - resolveArgs: [{ command?: string; statement?: { string: string } }]; strings?: string[]; }, handleArgs, ) => { + const sanitizedSqlQuery = this._sanitizeSqlQuery(handleThisArg.strings?.[0]); + return startSpanManual( { - name: 'postgresjs.query', + name: sanitizedSqlQuery || 'postgresjs.query', op: 'db', }, (span: Span) => { @@ -152,23 +136,20 @@ export class PostgresJsInstrumentation extends InstrumentationBase { const databaseName = postgresConnectionContext?.ATTR_DB_NAMESPACE || ''; const databaseHost = postgresConnectionContext?.ATTR_SERVER_ADDRESS || ''; const databasePort = postgresConnectionContext?.ATTR_SERVER_PORT || ''; - const sanitizedSqlQuery = this._sanitizeSqlQuery(handleThisArg.strings?.[0]); span.setAttribute(ATTR_DB_SYSTEM_NAME, 'postgres'); span.setAttribute(ATTR_DB_NAMESPACE, databaseName); span.setAttribute(ATTR_SERVER_ADDRESS, databaseHost); span.setAttribute(ATTR_SERVER_PORT, databasePort); - span.setAttribute(ATTR_DB_QUERY_TEXT, sanitizedSqlQuery); - handleThisArg.resolve = this._patchResolve(handleThisArg.resolve, span, databaseName); + handleThisArg.resolve = this._patchResolve(handleThisArg.resolve, span); handleThisArg.reject = this._patchReject(handleThisArg.reject, span); try { return Reflect.apply(handleTarget, handleThisArg, handleArgs); } catch (error) { span.setStatus({ - code: SpanStatusCode.ERROR, - message: (error as Error).message || 'PostgresJs Query Error', + code: SPAN_STATUS_ERROR, }); span.end(); throw error; // Re-throw the error to propagate it diff --git a/yarn.lock b/yarn.lock index 839ac64b50d7..12face1f0728 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26925,7 +26925,7 @@ string-template@~0.2.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27035,6 +27035,13 @@ stringify-object@^3.2.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -27056,13 +27063,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -27198,7 +27198,7 @@ stylus@0.59.0, stylus@^0.59.0: sax "~1.2.4" source-map "^0.7.3" -sucrase@^3.27.0, sucrase@^3.35.0: +sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: @@ -29833,19 +29833,19 @@ wrangler@^3.67.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== +wrap-ansi@7.0.0, wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" From edcc0164f7e7bcdfbafb1ce483a183d58fa3c013 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 25 Jun 2025 13:15:50 +0100 Subject: [PATCH 07/11] Add `requireParentSpan`, `requestHook` and `origin` --- .../suites/tracing/postgresjs/test.ts | 32 +++---- .../src/integrations/tracing/postgresjs.ts | 83 +++++++++++++++++-- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index deb460eb5e63..23cbab53ff54 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -11,7 +11,7 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), @@ -19,7 +19,7 @@ describe('postgresjs auto instrumentation', () => { 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -31,7 +31,7 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), @@ -39,7 +39,7 @@ describe('postgresjs auto instrumentation', () => { "select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid", op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -50,7 +50,7 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'sentry.op': 'db', 'server.address': 'localhost', 'server.port': 5444, @@ -58,7 +58,7 @@ describe('postgresjs auto instrumentation', () => { description: 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -70,14 +70,14 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), description: 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -89,14 +89,14 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), description: 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -108,14 +108,14 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), description: 'SELECT * from generate_series(?,?) as x', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -127,14 +127,14 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), description: 'DROP TABLE "User"', op: 'db', status: 'ok', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -146,14 +146,14 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'sentry.op': 'db', - 'sentry.origin': 'manual', + 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), description: 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', op: 'db', status: 'unknown_error', - origin: 'manual', + origin: 'auto.db.otel.postgres', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index efc64672bc0d..8452a0dd60da 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -1,9 +1,11 @@ // Instrumentation for https://github.com/porsager/postgres +import { context, trace } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, + safeExecuteInTheMiddle, } from '@opentelemetry/instrumentation'; import { ATTR_DB_NAMESPACE, @@ -12,20 +14,56 @@ import { ATTR_SERVER_PORT, } from '@opentelemetry/semantic-conventions'; import type { IntegrationFn, Span } from '@sentry/core'; -import { defineIntegration, getCurrentScope, SDK_VERSION, SPAN_STATUS_ERROR, startSpanManual } from '@sentry/core'; +import { + defineIntegration, + getCurrentScope, + logger, + SDK_VERSION, + SPAN_STATUS_ERROR, + startSpanManual, +} from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; +import { addOriginToSpan } from '../../utils/addOriginToSpan'; const INTEGRATION_NAME = 'PostgresJs'; const SUPPORTED_VERSIONS = ['>=3.0.0 <4']; -export const instrumentPostgresJs = generateInstrumentOnce(INTEGRATION_NAME, () => new PostgresJsInstrumentation()); +type PostgresConnectionContext = { + ATTR_DB_NAMESPACE?: string; // Database name + ATTR_SERVER_ADDRESS?: string; // Hostname or IP address of the database server + ATTR_SERVER_PORT?: string; // Port number of the database server +}; + +type PostgresJsInstrumentationConfig = InstrumentationConfig & { + /** + * Whether to require a parent span for the instrumentation. + * If set to true, the instrumentation will only create spans if there is a parent span + * available in the current scope. + * @default true + */ + requireParentSpan?: boolean; + /** + * Hook to modify the span before it is started. + * This can be used to set additional attributes or modify the span in any way. + */ + requestHook?: (span: Span, sanitizedSqlQuery: string, postgresConnectionContext?: PostgresConnectionContext) => void; +}; + +export const instrumentPostgresJs = generateInstrumentOnce( + INTEGRATION_NAME, + (options?: PostgresJsInstrumentationConfig) => + new PostgresJsInstrumentation({ + requireParentSpan: options?.requireParentSpan ?? true, + requestHook: options?.requestHook, + }), +); /** * Instrumentation for the [postgres](https://www.npmjs.com/package/postgres) library. * This instrumentation captures postgresjs queries and their attributes, */ -export class PostgresJsInstrumentation extends InstrumentationBase { - public constructor(config: InstrumentationConfig = {}) { +export class PostgresJsInstrumentation extends InstrumentationBase { + public constructor(config: PostgresJsInstrumentationConfig) { super('sentry-postgres-js', SDK_VERSION, config); } @@ -58,6 +96,17 @@ export class PostgresJsInstrumentation extends InstrumentationBase { return [instrumentationModule]; } + /** + * Determines whether a span should be created based on the current context. + * If `requireParentSpan` is set to true in the configuration, a span will + * only be created if there is a parent span available. + */ + private _shouldCreateSpans(): boolean { + const config = this.getConfig(); + const hasParentSpan = trace.getSpan(context.active()) !== undefined; + return hasParentSpan || !config.requireParentSpan; + } + /** * Patches the reject method of the Query class to set the span status and end it */ @@ -114,6 +163,11 @@ export class PostgresJsInstrumentation extends InstrumentationBase { }, handleArgs, ) => { + if (!this._shouldCreateSpans()) { + // If we don't need to create spans, just call the original method + return Reflect.apply(handleTarget, handleThisArg, handleArgs); + } + const sanitizedSqlQuery = this._sanitizeSqlQuery(handleThisArg.strings?.[0]); return startSpanManual( @@ -124,13 +178,24 @@ export class PostgresJsInstrumentation extends InstrumentationBase { (span: Span) => { const scope = getCurrentScope(); const postgresConnectionContext = scope.getScopeData().contexts['postgresjsConnection'] as - | { - ATTR_DB_NAMESPACE: string; - ATTR_SERVER_ADDRESS: string; - ATTR_SERVER_PORT: string; - } + | PostgresConnectionContext | undefined; + addOriginToSpan(span, 'auto.db.otel.postgres'); + + const { requestHook } = this.getConfig(); + + if (requestHook) { + safeExecuteInTheMiddle( + () => requestHook(span, sanitizedSqlQuery, postgresConnectionContext), + error => { + if (error) { + logger.error(`Error in requestHook for ${INTEGRATION_NAME} integration:`, error); + } + }, + ); + } + // ATTR_DB_NAMESPACE is used to indicate the database name and the schema name // It's only the database name as we don't have the schema information const databaseName = postgresConnectionContext?.ATTR_DB_NAMESPACE || ''; From 9798064611e379ef3f98c0fc956249f50bfe9a94 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 25 Jun 2025 15:37:05 +0100 Subject: [PATCH 08/11] Add more attributes --- .../suites/tracing/postgresjs/test.ts | 20 +++++++++++++++ .../src/integrations/tracing/postgresjs.ts | 25 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index 23cbab53ff54..83ea56a9fce3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -10,6 +10,9 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'CREATE TABLE', + 'db.query.text': + 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -30,6 +33,9 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'SELECT', + 'db.query.text': + "select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid", 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -50,6 +56,8 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'INSERT', + 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', 'sentry.origin': 'auto.db.otel.postgres', 'sentry.op': 'db', 'server.address': 'localhost', @@ -69,6 +77,8 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'UPDATE', + 'db.query.text': 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -88,6 +98,8 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'SELECT', + 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -107,6 +119,8 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'SELECT', + 'db.query.text': 'SELECT * from generate_series(?,?) as x', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -126,6 +140,8 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + 'db.operation.name': 'DROP TABLE', + 'db.query.text': 'DROP TABLE "User"', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', @@ -145,6 +161,10 @@ describe('postgresjs auto instrumentation', () => { data: expect.objectContaining({ 'db.namespace': 'test_db', 'db.system.name': 'postgres', + // No db.operation.name here, as this is an errored span + 'db.response.status_code': '42P01', + 'error.type': 'PostgresError', + 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index 8452a0dd60da..c5efb7f6bef7 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -9,7 +9,11 @@ import { } from '@opentelemetry/instrumentation'; import { ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, + ATTR_DB_RESPONSE_STATUS_CODE, ATTR_DB_SYSTEM_NAME, + ATTR_ERROR_TYPE, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, } from '@opentelemetry/semantic-conventions'; @@ -117,13 +121,25 @@ export class PostgresJsInstrumentation extends InstrumentationBase { span.setStatus({ code: SPAN_STATUS_ERROR, + // This message is the error message from the rejectArgs, when available + // e.g "relation 'User' does not exist" + message: rejectArgs?.[0]?.message || 'unknown_error', }); const result = Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); + + // This status code is PG error code, e.g. '42P01' for "relation does not exist" + // https://www.postgresql.org/docs/current/errcodes-appendix.html + span.setAttribute(ATTR_DB_RESPONSE_STATUS_CODE, rejectArgs?.[0]?.code || 'Unknown error'); + // This is the error type, e.g. 'PostgresError' for a Postgres error + span.setAttribute(ATTR_ERROR_TYPE, rejectArgs?.[0]?.name || 'Unknown error'); + span.end(); return result; }, @@ -135,8 +151,14 @@ export class PostgresJsInstrumentation extends InstrumentationBase { + apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string }]) => { const result = Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); + const sqlCommand = resolveArgs?.[0]?.command; + + if (sqlCommand) { + // SQL command is only available when the query is resolved successfully + span.setAttribute(ATTR_DB_OPERATION_NAME, sqlCommand); + } span.end(); return result; }, @@ -206,6 +228,7 @@ export class PostgresJsInstrumentation extends InstrumentationBase Date: Wed, 25 Jun 2025 15:46:34 +0100 Subject: [PATCH 09/11] Move test emails to constants --- .../suites/tracing/postgresjs/test.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index 83ea56a9fce3..75e65beccb21 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -1,6 +1,9 @@ import { describe, expect, test } from 'vitest'; import { createRunner } from '../../../utils/runner'; +const EXISTING_TEST_EMAIL = 'bar@baz.com'; +const NON_EXISTING_TEST_EMAIL = 'foo@baz.com'; + describe('postgresjs auto instrumentation', () => { test('should auto-instrument `postgres` package', { timeout: 60_000 }, async () => { const EXPECTED_TRANSACTION = { @@ -57,13 +60,13 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', + 'db.query.text': `INSERT INTO "User" ("email", "name") VALUES (\'Foo\', '${EXISTING_TEST_EMAIL}')`, 'sentry.origin': 'auto.db.otel.postgres', 'sentry.op': 'db', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'INSERT INTO "User" ("email", "name") VALUES (\'Foo\', \'bar@baz.com\')', + description: `INSERT INTO "User" ("email", "name") VALUES (\'Foo\', '${EXISTING_TEST_EMAIL}')`, op: 'db', status: 'ok', origin: 'auto.db.otel.postgres', @@ -78,13 +81,13 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'db.operation.name': 'UPDATE', - 'db.query.text': 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', + 'db.query.text': `UPDATE "User" SET "name" = 'Foo' WHERE "email" = '${EXISTING_TEST_EMAIL}'`, 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'UPDATE "User" SET "name" = \'Foo\' WHERE "email" = \'bar@baz.com\'', + description: `UPDATE "User" SET "name" = 'Foo' WHERE "email" = '${EXISTING_TEST_EMAIL}'`, op: 'db', status: 'ok', origin: 'auto.db.otel.postgres', @@ -99,13 +102,13 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', + 'db.query.text': `SELECT * FROM "User" WHERE "email" = '${EXISTING_TEST_EMAIL}'`, 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT * FROM "User" WHERE "email" = \'bar@baz.com\'', + description: `SELECT * FROM "User" WHERE "email" = '${EXISTING_TEST_EMAIL}'`, op: 'db', status: 'ok', origin: 'auto.db.otel.postgres', @@ -164,13 +167,13 @@ describe('postgresjs auto instrumentation', () => { // No db.operation.name here, as this is an errored span 'db.response.status_code': '42P01', 'error.type': 'PostgresError', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', + 'db.query.text': `SELECT * FROM "User" WHERE "email" = '${NON_EXISTING_TEST_EMAIL}'`, 'sentry.op': 'db', 'sentry.origin': 'auto.db.otel.postgres', 'server.address': 'localhost', 'server.port': 5444, }), - description: 'SELECT * FROM "User" WHERE "email" = \'foo@baz.com\'', + description: `SELECT * FROM "User" WHERE "email" = '${NON_EXISTING_TEST_EMAIL}'`, op: 'db', status: 'unknown_error', origin: 'auto.db.otel.postgres', From 92e5f3320b6a97685d9f1906dea02e17c1c2d20c Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 25 Jun 2025 16:06:45 +0100 Subject: [PATCH 10/11] Lint --- .../node-integration-tests/suites/tracing/postgresjs/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts index 75e65beccb21..68b1a82703a0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -60,13 +60,13 @@ describe('postgresjs auto instrumentation', () => { 'db.namespace': 'test_db', 'db.system.name': 'postgres', 'db.operation.name': 'INSERT', - 'db.query.text': `INSERT INTO "User" ("email", "name") VALUES (\'Foo\', '${EXISTING_TEST_EMAIL}')`, + 'db.query.text': `INSERT INTO "User" ("email", "name") VALUES ('Foo', '${EXISTING_TEST_EMAIL}')`, 'sentry.origin': 'auto.db.otel.postgres', 'sentry.op': 'db', 'server.address': 'localhost', 'server.port': 5444, }), - description: `INSERT INTO "User" ("email", "name") VALUES (\'Foo\', '${EXISTING_TEST_EMAIL}')`, + description: `INSERT INTO "User" ("email", "name") VALUES ('Foo', '${EXISTING_TEST_EMAIL}')`, op: 'db', status: 'ok', origin: 'auto.db.otel.postgres', From 3d373cc6a2ae1e58578cb2ed9b124ba7f7d2cebd Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 26 Jun 2025 14:52:03 +0100 Subject: [PATCH 11/11] Export integration --- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + 7 files changed, 7 insertions(+) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 750eb05d8b10..83a135e71f21 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -84,6 +84,7 @@ export { onUnhandledRejectionIntegration, parameterize, postgresIntegration, + postgresJsIntegration, prismaIntegration, childProcessIntegration, createSentryWinstonTransport, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index b13f69a9b6ce..f64ee53dc47c 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -99,6 +99,7 @@ export { redisIntegration, tediousIntegration, postgresIntegration, + postgresJsIntegration, prismaIntegration, childProcessIntegration, createSentryWinstonTransport, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 14a44e2d38fc..4a9d7fd9d71c 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -120,6 +120,7 @@ export { redisIntegration, tediousIntegration, postgresIntegration, + postgresJsIntegration, prismaIntegration, hapiIntegration, setupHapiErrorHandler, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index e9586a9bd820..f0bed369acee 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -99,6 +99,7 @@ export { redisIntegration, tediousIntegration, postgresIntegration, + postgresJsIntegration, prismaIntegration, hapiIntegration, setupHapiErrorHandler, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index cf951c3db8b6..1c02da9fff2e 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -23,6 +23,7 @@ export { mysqlIntegration } from './integrations/tracing/mysql'; export { mysql2Integration } from './integrations/tracing/mysql2'; export { redisIntegration } from './integrations/tracing/redis'; export { postgresIntegration } from './integrations/tracing/postgres'; +export { postgresJsIntegration } from './integrations/tracing/postgresjs'; export { prismaIntegration } from './integrations/tracing/prisma'; export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi'; export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index f11b9bb51077..9574b38f1e47 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -75,6 +75,7 @@ export { onUnhandledRejectionIntegration, parameterize, postgresIntegration, + postgresJsIntegration, prismaIntegration, redisIntegration, requestDataIntegration, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 717dd7387c98..07d92d03c8ce 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -77,6 +77,7 @@ export { onUnhandledRejectionIntegration, parameterize, postgresIntegration, + postgresJsIntegration, prismaIntegration, redisIntegration, requestDataIntegration,