From 34a05f1efda94b18dbf2ac438ae5159add0694a7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Oct 2026 16:57:01 +0200 Subject: [PATCH 1/6] test(node): Port Postgres integration tests to span streaming Co-Authored-By: GPT-6 --- .../node-suites/excludes.ts | 2 - .../postgres-streamed/docker-compose.yml | 18 - .../instrument-ignoreConnect.mjs | 11 - .../tracing/postgres-streamed/instrument.mjs | 10 - .../postgres-streamed/scenario-native.mjs | 47 -- .../tracing/postgres-streamed/scenario.mjs | 41 - .../suites/tracing/postgres-streamed/test.ts | 283 ------- .../postgres/instrument-ignoreConnect.mjs | 1 - .../instrument-orchestrion-ignoreConnect.mjs | 1 - .../postgres/instrument-orchestrion.mjs | 1 - .../suites/tracing/postgres/instrument.mjs | 1 - .../suites/tracing/postgres/test.ts | 770 +++++++++++------- .../postgresjs-streamed/docker-compose.yml | 18 - .../instrument-requestHook.mjs | 26 - .../postgresjs-streamed/instrument.mjs | 9 - .../scenario-requestHook.mjs | 39 - .../postgresjs-streamed/scenario-unsafe.mjs | 37 - .../postgresjs-streamed/scenario-url.mjs | 75 -- .../tracing/postgresjs-streamed/scenario.mjs | 74 -- .../tracing/postgresjs-streamed/test.ts | 325 -------- .../postgresjs/instrument-requestHook.mjs | 16 +- .../suites/tracing/postgresjs/instrument.mjs | 1 - .../suites/tracing/postgresjs/test.ts | 668 ++++++++------- 23 files changed, 854 insertions(+), 1620 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/docker-compose.yml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument-ignoreConnect.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario-native.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/docker-compose.yml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument-requestHook.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-requestHook.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-unsafe.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-url.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index 5b99ffc866ab..2fa45c44aaa6 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -125,9 +125,7 @@ export const NO_AUTO_INSTRUMENTATION = [ 'suites/tracing/openai/v7/test.ts', 'suites/tracing/orchestrion-lazy-registration/test.ts', 'suites/tracing/pi-durable/test.ts', - 'suites/tracing/postgres-streamed/test.ts', 'suites/tracing/postgres/test.ts', - 'suites/tracing/postgresjs-streamed/test.ts', 'suites/tracing/postgresjs/test.ts', 'suites/tracing/prisma-orm-v5/test.ts', 'suites/tracing/prisma-orm-v6/test.ts', diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/docker-compose.yml deleted file mode 100644 index 05eebd27e0f0..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '3.9' - -services: - db: - image: postgres:13 - restart: always - ports: - - '5495:5432' - environment: - POSTGRES_USER: test - POSTGRES_PASSWORD: test - POSTGRES_DB: tests - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U test -d tests'] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument-ignoreConnect.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument-ignoreConnect.mjs deleted file mode 100644 index fbf2369478ca..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument-ignoreConnect.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [Sentry.postgresIntegration({ ignoreConnectSpans: true })], - transport: loggingTransport, - traceLifecycle: 'stream', -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument.mjs deleted file mode 100644 index 53b9511a21f0..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/instrument.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, - traceLifecycle: 'stream', -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario-native.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario-native.mjs deleted file mode 100644 index bb229c2eb729..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario-native.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; -import pg from 'pg'; - -const { native } = pg; -const { Client } = native; - -// `pg-native` uses libpq, which resolves `localhost` to IPv6 (`::1`) first and does not -// fall back to IPv4. Docker Desktop only forwards the mapped port over IPv4, so we connect -// to the IPv4 loopback explicitly to avoid an `ECONNREFUSED` on `::1`. -const connectionConfig = { host: '127.0.0.1', port: 5495, user: 'test', password: 'test', database: 'tests' }; -const client = new Client(connectionConfig); - -async function run() { - // Gate on the DB actually accepting a connection before opening the span (see `waitForConnection`). - await waitForConnection(async () => { - const probe = new Client(connectionConfig); - await probe.connect(); - await probe.end(); - }); - - await Sentry.startSpan( - { - name: 'Test Span', - op: 'parent_span', - }, - async () => { - try { - await client.connect(); - - await client.query( - 'CREATE TABLE "NativeUser" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"));', - ); - - const email = `${uuid4()}@domain.com`; - await client.query('INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)', [email, 'tim']); - await client.query('SELECT * FROM "NativeUser"'); - } finally { - await client.query('DROP TABLE "NativeUser"'); - await client.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario.mjs deleted file mode 100644 index 81b9e08f910c..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/scenario.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; -import { Client } from 'pg'; - -const connectionConfig = { port: 5495, user: 'test', password: 'test', database: 'tests' }; -const client = new Client(connectionConfig); - -async function run() { - // Gate on the DB actually accepting a connection before opening the span (see `waitForConnection`). - await waitForConnection(async () => { - const probe = new Client(connectionConfig); - await probe.connect(); - await probe.end(); - }); - - await Sentry.startSpan( - { - name: 'Test Span', - op: 'parent_span', - }, - async () => { - try { - await client.connect(); - - await client.query( - '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"));', - ); - - const email = `${uuid4()}@domain.com`; - await client.query('INSERT INTO "User" ("email", "name") VALUES ($1, $2)', [email, 'tim']); - await client.query('SELECT * FROM "User"'); - } finally { - await client.query('DROP TABLE "User"'); - await client.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts deleted file mode 100644 index bec779c70b02..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import type { SerializedStreamedSpanContainer } from '@sentry/core'; -import { SENTRY_TRACE_LIFECYCLE, SENTRY_OP } from '@sentry/conventions/attributes'; -import { afterAll, describe, expect } from 'vitest'; -import { conditionalTest, EXPECTED_SDK_NAME } from '../../../utils'; -import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; - -// Query-span origin depends on which instrumentation is active. Blocks driving the SDK's default -// integrations get the diagnostics-channel origin when the generic orchestrion run is enabled (via -// INJECT_ORCHESTRION), since the OTel `Postgres` integration is then swapped for the channel one. Blocks -// that pass an explicit `postgresIntegration()` (e.g. `ignoreConnectSpans`) keep the OTel origin. -const QUERY_ORIGIN = 'auto.db.postgres'; - -const COMMON_DB_ATTRIBUTES = { - 'sentry.is_localhost': { - type: 'boolean', - value: false, - }, - 'db.connection_string': { - type: 'string', - value: expect.stringMatching(/^postgresql:\/\/localhost:\d+\/tests$/), - }, - 'db.namespace': { - type: 'string', - value: 'tests', - }, - 'db.system.name': { - type: 'string', - value: 'postgresql', - }, - 'db.user': { - type: 'string', - value: 'test', - }, - 'server.address': { - type: 'string', - value: 'localhost', - }, - 'server.port': { - type: 'integer', - value: expect.any(Number), - }, - 'sentry.kind': { - type: 'string', - value: 'client', - }, - 'sentry.environment': { - type: 'string', - value: 'production', - }, - 'sentry.op': { - type: 'string', - value: 'db', - }, - 'sentry.release': { - type: 'string', - value: '1.0', - }, - 'sentry.sdk.name': { - type: 'string', - value: EXPECTED_SDK_NAME, - }, - 'sentry.sdk.version': { - type: 'string', - value: expect.any(String), - }, - 'sentry.segment.id': { - type: 'string', - value: expect.stringMatching(/^[\da-f]{16}$/), - }, - 'sentry.segment.name': { - type: 'string', - value: 'Test Span', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, -}; - -/** - * Builds the expected strict shape of a streamed postgres db span. - * - * Query spans carry a `db.statement` and the query origin (`auto.db.postgres`, or - * `auto.db.postgres` under the generic orchestrion run — see `QUERY_ORIGIN`). The - * `pg.connect` span has no `db.statement`, and since the pg instrumentation sets no origin on it, it - * carries the default `manual` origin (written as an attribute on the streamed-span path; the - * non-streamed/SDK path omits the `manual` default). - * - * `host` defaults to `localhost`, but the `pg-native` scenarios connect to the IPv4 loopback - * (`127.0.0.1`) explicitly, so the reported peer name and connection string reflect that. - * - * `origin` defaults to `QUERY_ORIGIN`; blocks that force the OTel path (explicit `postgresIntegration()`) - * pass `auto.db.postgres` explicitly. - */ -function expectedDbSpan({ - name, - statement, - host = 'localhost', - origin = QUERY_ORIGIN, -}: { - name: string; - statement?: string; - host?: string; - origin?: string; -}): unknown { - const attributes: Record = { - ...COMMON_DB_ATTRIBUTES, - 'server.address': { - type: 'string', - value: host, - }, - 'db.connection_string': { - type: 'string', - value: expect.stringMatching(new RegExp(`^postgresql://${host.replace(/\./g, '\\.')}:\\d+/tests$`)), - }, - }; - - if (statement) { - attributes['db.query.text'] = { - type: 'string', - value: statement, - }; - // The name of a db query span is its `db.query.summary` attribute - attributes['db.query.summary'] = { - type: 'string', - value: name, - }; - attributes['sentry.origin'] = { - type: 'string', - value: origin, - }; - } else { - attributes['sentry.origin'] = { - type: 'string', - value: 'manual', - }; - } - - return { - attributes, - end_timestamp: expect.any(Number), - is_segment: false, - name, - parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - }; -} - -const CREATE_USER_TABLE_STATEMENT = - '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"))'; - -const CREATE_NATIVE_USER_TABLE_STATEMENT = - 'CREATE TABLE "NativeUser" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))'; - -function getDbSpans(container: SerializedStreamedSpanContainer): SerializedStreamedSpanContainer['items'] { - return container.items.filter(item => item.attributes[SENTRY_OP]?.value === 'db'); -} - -describeWithDockerCompose('postgres auto instrumentation (streamed)', { workingDirectory: [__dirname] }, () => { - afterAll(() => { - cleanupChildProcesses(); - }); - - describe('default', () => { - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('should auto-instrument `pg` package with span streaming enabled', { timeout: 90_000 }, async () => { - await createTestRunner() - .expect({ - span: container => { - const segmentSpan = container.items.find(item => item.is_segment); - expect(segmentSpan?.name).toBe('Test Span'); - - const dbSpans = getDbSpans(container); - expect(dbSpans.length).toBe(5); - - expect(dbSpans).toEqual([ - expectedDbSpan({ name: 'pg.connect' }), - expectedDbSpan({ name: 'CREATE TABLE "User"', statement: CREATE_USER_TABLE_STATEMENT }), - expectedDbSpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - }), - expectedDbSpan({ name: 'SELECT "User"', statement: 'SELECT * FROM "User"' }), - expectedDbSpan({ name: 'DROP TABLE "User"', statement: 'DROP TABLE "User"' }), - ]); - }, - }) - .start() - .completed(); - }); - }); - }); - - describe('ignoreConnectSpans', () => { - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-ignoreConnect.mjs', (createTestRunner, test) => { - test("doesn't emit connect spans if ignoreConnectSpans is true", { timeout: 90_000 }, async () => { - await createTestRunner() - .expect({ - span: container => { - const dbSpans = getDbSpans(container); - expect(dbSpans.find(span => span.name.includes('connect'))).toBeUndefined(); - expect(dbSpans.length).toBe(4); - - // `postgresIntegration()` is the diagnostics-channel implementation by default, so query - // spans carry the orchestrion origin even when passing explicit options like - // `ignoreConnectSpans`. - const origin = 'auto.db.postgres'; - expect(dbSpans).toEqual([ - expectedDbSpan({ name: 'CREATE TABLE "User"', statement: CREATE_USER_TABLE_STATEMENT, origin }), - expectedDbSpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - origin, - }), - expectedDbSpan({ name: 'SELECT "User"', statement: 'SELECT * FROM "User"', origin }), - expectedDbSpan({ name: 'DROP TABLE "User"', statement: 'DROP TABLE "User"', origin }), - ]); - }, - }) - .start() - .completed(); - }); - }); - }); - - // Deno: with a module load hook installed, Deno compiles a native addon (`libpq`) as JavaScript. - // Bun: the `libpq` addon needs the Node symbol `node::EmitAsyncInit`, which Bun does not provide. - conditionalTest({ max: 25, skipRuntimes: ['bun', 'deno'] })('pg-native', () => { - createEsmAndCjsTests( - __dirname, - 'scenario-native.mjs', - 'instrument.mjs', - (createTestRunner, test) => { - test( - 'should auto-instrument `pg-native` package with span streaming enabled', - { timeout: 120_000 }, - async () => { - await createTestRunner() - .expect({ - span: container => { - const segmentSpan = container.items.find(item => item.is_segment); - expect(segmentSpan?.name).toBe('Test Span'); - - const dbSpans = getDbSpans(container); - expect(dbSpans.length).toBe(5); - - expect(dbSpans).toEqual([ - expectedDbSpan({ name: 'pg.connect', host: '127.0.0.1' }), - expectedDbSpan({ - name: 'CREATE TABLE "NativeUser"', - statement: CREATE_NATIVE_USER_TABLE_STATEMENT, - host: '127.0.0.1', - }), - expectedDbSpan({ - name: 'INSERT "NativeUser"', - statement: 'INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)', - host: '127.0.0.1', - }), - expectedDbSpan({ - name: 'SELECT "NativeUser"', - statement: 'SELECT * FROM "NativeUser"', - host: '127.0.0.1', - }), - expectedDbSpan({ - name: 'DROP TABLE "NativeUser"', - statement: 'DROP TABLE "NativeUser"', - host: '127.0.0.1', - }), - ]); - }, - }) - .start() - .completed(); - }, - ); - }, - { additionalDependencies: { 'pg-native': '3.7.0', pg: '8.20.0' } }, - ); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-ignoreConnect.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-ignoreConnect.mjs index 9ce51efd35ff..cd9240108020 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-ignoreConnect.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-ignoreConnect.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion-ignoreConnect.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion-ignoreConnect.mjs index c349b52dddd4..0d2a99ce3314 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion-ignoreConnect.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion-ignoreConnect.mjs @@ -6,7 +6,6 @@ import { loggingTransport } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion.mjs index a1923d42be9b..50479ee983af 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument-orchestrion.mjs @@ -5,7 +5,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts index 6533e2cca588..a201cd95d1d2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts @@ -1,7 +1,56 @@ +import { + DB_CONNECTION_STRING, + DB_NAMESPACE, + DB_QUERY_SUMMARY, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + DB_USER, + SENTRY_ENVIRONMENT, + SENTRY_IS_LOCALHOST, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_RELEASE, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_TRACE_LIFECYCLE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import { afterAll, describe, expect } from 'vitest'; -import { conditionalTest } from '../../../utils'; +import { conditionalTest, EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; +const COMMON_DB_ATTRIBUTES = { + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + [DB_CONNECTION_STRING]: { type: 'string', value: expect.stringMatching(/^postgresql:\/\/localhost:\d+\/tests$/) }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_USER]: { type: 'string', value: 'test' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: expect.any(Number) }, + [SENTRY_KIND]: { type: 'string', value: 'client' }, + [SENTRY_ENVIRONMENT]: { type: 'string', value: 'production' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_RELEASE]: { type: 'string', value: '1.0' }, + [SENTRY_SDK_NAME]: { type: 'string', value: EXPECTED_SDK_NAME }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Test Transaction' }, + [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream' }, +}; + +const COMMON_SPAN_FIELDS = { + end_timestamp: expect.any(Number), + is_segment: false, + parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + trace_id: expect.stringMatching(/^[\da-f]{32}$/), +}; + describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [__dirname] }, () => { afterAll(() => { cleanupChildProcesses(); @@ -12,79 +61,116 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ const QUERY_ORIGIN = 'auto.db.postgres'; describe('default', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'sentry.origin': 'manual', - 'sentry.op': 'db', - }), - description: 'pg.connect', - op: 'db', + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + }, + name: 'pg.connect', status: 'ok', - }), - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - op: 'db', + }, + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + }, + name: 'INSERT "User"', status: 'ok', - origin: QUERY_ORIGIN, - }), - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "User"', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'SELECT * FROM "User"', - op: 'db', + }, + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + }, + name: 'SELECT "User"', status: 'ok', - origin: QUERY_ORIGIN, - }), + }, expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'db.postgresql.plan': 'select-user-by-email', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + 'db.postgresql.plan': { type: 'string', value: 'select-user-by-email' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: QUERY_ORIGIN, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "does_not_exist_table"', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "does_not_exist_table"' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, }), - description: 'SELECT * FROM "does_not_exist_table"', - op: 'db', - status: 'internal_error', - origin: QUERY_ORIGIN, + name: 'SELECT "does_not_exist_table"', + status: 'error', }), + { + ...COMMON_SPAN_FIELDS, + name: 'CREATE TABLE "User"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + '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_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, + }, + }, + { + ...COMMON_SPAN_FIELDS, + name: 'DROP TABLE "User"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, + }, + }, ]), }; createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test) => { test('should auto-instrument `pg` package', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner() + .expect({ + span: container => { + expect(container).toMatchObject(EXPECTED_SPANS); + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(7); + }, + }) + .start() + .completed(); }); }); }); @@ -94,38 +180,72 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ test("doesn't emit connect spans if ignoreConnectSpans is true", { timeout: 90_000 }, async () => { await createTestRunner() .expect({ - transaction: txn => { - const spanNames = txn.spans?.map(span => span.description); + span: container => { + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(6); + const spanNames = container.items.map(span => span.name); expect(spanNames?.find(name => name?.includes('connect'))).toBeUndefined(); - expect(txn).toMatchObject({ - transaction: 'Test Transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - op: 'db', + expect(container).toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', + }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + }, + name: 'INSERT "User"', status: 'ok', - origin: QUERY_ORIGIN, - }), - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "User"', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'SELECT * FROM "User"', - op: 'db', + }, + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + }, + name: 'SELECT "User"', status: 'ok', - origin: QUERY_ORIGIN, - }), + }, + { + ...COMMON_SPAN_FIELDS, + name: 'CREATE TABLE "User"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + '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_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, + }, + }, + { + ...COMMON_SPAN_FIELDS, + name: 'DROP TABLE "User"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, + }, + }, ]), }); }, @@ -137,36 +257,34 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ }); describe('pool', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), // Pool connect span: no origin is set on connect spans, so it defaults // to 'manual', and the connection-string credentials are masked out. expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.connection_string': 'postgresql://localhost:5494/tests', - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_CONNECTION_STRING]: { type: 'string', value: 'postgresql://localhost:5494/tests' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), - description: 'pg-pool.connect', - op: 'db', + name: 'pg-pool.connect', status: 'ok', - origin: 'manual', }), // Callback-style query (no awaited promise returned to the caller). expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT ? AS foo', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS foo' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), - description: 'SELECT ? AS foo', - op: 'db', + name: 'SELECT', status: 'ok', - origin: QUERY_ORIGIN, }), ]), }; @@ -176,26 +294,25 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'auto-instruments `pg.Pool`, masks connection-string credentials, and handles callback-style queries', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }, ); }); }); describe('connect error', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), - description: 'pg.connect', - op: 'db', - status: 'internal_error', - origin: 'manual', + name: 'pg.connect', + status: 'error', }), ]), }; @@ -203,7 +320,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ // No DB needed: the scenario connects to a port where nothing is listening. createEsmAndCjsTests(__dirname, 'scenario-connect-error.mjs', 'instrument.mjs', (createTestRunner, test) => { test('records an errored connect span when the connection fails', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); }); @@ -213,28 +330,27 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ // parent span, the query only produces a span if the trace context survives the // connect promise's continuation. describe('connect promise continuation', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT ? AS connect_then', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS connect_then' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), - description: 'SELECT ? AS connect_then', - op: 'db', + name: 'SELECT', status: 'ok', - origin: QUERY_ORIGIN, }), ]), }; createEsmAndCjsTests(__dirname, 'scenario-connect-then.mjs', 'instrument.mjs', (createTestRunner, test) => { test('parents a query chained off connect() to the active transaction', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); }); @@ -244,27 +360,28 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ test('does not instrument queries or connects without an active parent span', { timeout: 90_000 }, async () => { await createTestRunner() .expect({ - transaction: txn => { - const descriptions = txn.spans?.map(span => span.description) ?? []; + span: container => { + const descriptions = container.items.map(span => span.name); // The unparented connect + query must not have produced spans - expect(descriptions).not.toContain('SELECT ? AS unparented'); + expect(container.items.map(span => span.attributes[DB_QUERY_TEXT]?.value)).not.toContain( + 'SELECT ? AS unparented', + ); expect(descriptions.find(name => name?.includes('connect'))).toBeUndefined(); // Only the parented query is instrumented - expect(txn).toMatchObject({ - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + expect(container).toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT ? AS parented', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS parented' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), - description: 'SELECT ? AS parented', - op: 'db', + name: 'SELECT', status: 'ok', - origin: QUERY_ORIGIN, }), ]), }); @@ -279,46 +396,100 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ // Deno: with a module load hook installed, Deno compiles a native addon (`libpq`) as JavaScript. // Bun: the `libpq` addon needs the Node symbol `node::EmitAsyncInit`, which Bun does not provide. conditionalTest({ max: 25, skipRuntimes: ['bun', 'deno'] })('pg-native', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'sentry.origin': 'manual', - 'sentry.op': 'db', - }), - description: 'pg.connect', - op: 'db', + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, + [DB_CONNECTION_STRING]: { + type: 'string', + value: expect.stringMatching(/^postgresql:\/\/127\.0\.0\.1:\d+\/tests$/), + }, + }, + name: 'pg.connect', status: 'ok', - }), - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)', - op: 'db', + }, + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "NativeUser"' }, + [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, + [DB_CONNECTION_STRING]: { + type: 'string', + value: expect.stringMatching(/^postgresql:\/\/127\.0\.0\.1:\d+\/tests$/), + }, + }, + name: 'INSERT "NativeUser"', status: 'ok', - origin: QUERY_ORIGIN, - }), - expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "NativeUser"', - 'sentry.origin': QUERY_ORIGIN, - 'sentry.op': 'db', - }), - description: 'SELECT * FROM "NativeUser"', - op: 'db', + }, + { + ...COMMON_SPAN_FIELDS, + attributes: { + ...COMMON_DB_ATTRIBUTES, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "NativeUser"' }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "NativeUser"' }, + [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, + [DB_CONNECTION_STRING]: { + type: 'string', + value: expect.stringMatching(/^postgresql:\/\/127\.0\.0\.1:\d+\/tests$/), + }, + }, + name: 'SELECT "NativeUser"', status: 'ok', - origin: QUERY_ORIGIN, - }), + }, + { + ...COMMON_SPAN_FIELDS, + name: 'CREATE TABLE "NativeUser"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, + [DB_CONNECTION_STRING]: { + type: 'string', + value: expect.stringMatching(/^postgresql:\/\/127\.0\.0\.1:\d+\/tests$/), + }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + 'CREATE TABLE "NativeUser" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', + }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "NativeUser"' }, + }, + }, + { + ...COMMON_SPAN_FIELDS, + name: 'DROP TABLE "NativeUser"', + status: 'ok', + attributes: { + ...COMMON_DB_ATTRIBUTES, + [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, + [DB_CONNECTION_STRING]: { + type: 'string', + value: expect.stringMatching(/^postgresql:\/\/127\.0\.0\.1:\d+\/tests$/), + }, + [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "NativeUser"' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "NativeUser"' }, + }, + }, ]), }; @@ -328,7 +499,15 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'instrument.mjs', (createTestRunner, test) => { test('should auto-instrument `pg-native` package', { timeout: 120_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner() + .expect({ + span: container => { + expect(container).toMatchObject(EXPECTED_SPANS); + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(5); + }, + }) + .start() + .completed(); }); }, { additionalDependencies: { 'pg-native': '3.7.0', pg: '8.20.0' } }, @@ -343,126 +522,115 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ const ORIGIN = 'auto.db.postgres'; describe('default', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'sentry.origin': 'manual', - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, }), - description: 'pg.connect', - op: 'db', + name: 'pg.connect', status: 'ok', }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'db.postgresql.plan': 'select-user-by-email', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + 'db.postgresql.plan': { type: 'string', value: 'select-user-by-email' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.query.text': 'SELECT * FROM "does_not_exist_table"', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "does_not_exist_table"' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, }), - description: 'SELECT * FROM "does_not_exist_table"', - op: 'db', - status: 'internal_error', - origin: ORIGIN, + name: 'SELECT "does_not_exist_table"', + status: 'error', }), ]), }; createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-orchestrion.mjs', (createTestRunner, test) => { test('auto-instruments `pg` via diagnostics channels', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); }); describe('pool', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.connection_string': 'postgresql://localhost:5494/tests', - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_CONNECTION_STRING]: { type: 'string', value: 'postgresql://localhost:5494/tests' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), - description: 'pg-pool.connect', - op: 'db', + name: 'pg-pool.connect', status: 'ok', - origin: 'manual', }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT ? AS foo', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS foo' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), - description: 'SELECT ? AS foo', - op: 'db', + name: 'SELECT', status: 'ok', - origin: ORIGIN, }), ]), }; createEsmAndCjsTests(__dirname, 'scenario-pool.mjs', 'instrument-orchestrion.mjs', (createTestRunner, test) => { test('auto-instruments `pg.Pool` and handles callback-style queries', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); }); describe('connect error', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), - description: 'pg.connect', - op: 'db', - // A failed connect has no canonical status message, so serializes - // to `internal_error` (same as OTel). Holds across Node versions - // even though the error class differs: plain `Error` on Node 18, - // `AggregateError` on Node 20+. - status: 'internal_error', - origin: 'manual', + name: 'pg.connect', + status: 'error', }), ]), }; @@ -473,7 +641,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'instrument-orchestrion.mjs', (createTestRunner, test) => { test('records an errored connect span when the connection fails', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }, ); @@ -491,24 +659,25 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ async () => { await createTestRunner() .expect({ - transaction: txn => { - const descriptions = txn.spans?.map(span => span.description) ?? []; - expect(descriptions).not.toContain('SELECT ? AS unparented'); + span: container => { + const descriptions = container.items.map(span => span.name); + expect(container.items.map(span => span.attributes[DB_QUERY_TEXT]?.value)).not.toContain( + 'SELECT ? AS unparented', + ); expect(descriptions.find(name => name?.includes('connect'))).toBeUndefined(); - expect(txn).toMatchObject({ - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + expect(container).toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.query.text': 'SELECT ? AS parented', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS parented' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), - description: 'SELECT ? AS parented', - op: 'db', + name: 'SELECT', status: 'ok', - origin: ORIGIN, }), ]), }); @@ -534,39 +703,40 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ async () => { await createTestRunner() .expect({ - transaction: txn => { - const spanNames = txn.spans?.map(span => span.description); + span: container => { + const spanNames = container.items.map(span => span.name); // No `pg.connect` / `pg-pool.connect` spans were produced. expect(spanNames?.find(name => name?.includes('connect'))).toBeUndefined(); // ...but the query spans are still instrumented via orchestrion. - expect(txn).toMatchObject({ - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + expect(container).toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', + }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.system.name': 'postgresql', - 'db.namespace': 'tests', - 'db.query.text': 'SELECT * FROM "User"', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', + attributes: expect.objectContaining({ + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, + [DB_NAMESPACE]: { type: 'string', value: 'tests' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User"', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, }), ]), }); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/docker-compose.yml deleted file mode 100644 index e3e13e347bf6..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '3.9' - -services: - db: - image: postgres:13 - restart: always - ports: - - '5446:5432' - environment: - POSTGRES_USER: test - POSTGRES_PASSWORD: test - POSTGRES_DB: test_db - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U test -d test_db'] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument-requestHook.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument-requestHook.mjs deleted file mode 100644 index a95f4861f159..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument-requestHook.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -const requestHook = (span, sanitizedSqlQuery, connectionContext) => { - // Add custom attributes to demonstrate requestHook functionality. - // Streamed spans carry no `extra`, so the connection context is asserted via span attributes - // rather than `Sentry.setExtra` (as the static-lifecycle suite does). - span.setAttributes({ - 'custom.requestHook': 'called', - 'custom.requestHook.query': sanitizedSqlQuery, - 'custom.requestHook.database': connectionContext?.ATTR_DB_NAMESPACE, - 'custom.requestHook.host': connectionContext?.ATTR_SERVER_ADDRESS, - 'custom.requestHook.port': connectionContext?.ATTR_SERVER_PORT, - }); -}; - -// `postgresJsIntegration()` is the diagnostics-channel implementation by default; it forwards the -// `requestHook` to the channel subscriber. -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, - integrations: [Sentry.postgresJsIntegration({ requestHook })], - traceLifecycle: 'stream', -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument.mjs deleted file mode 100644 index 46a27dd03b74..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/instrument.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-requestHook.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-requestHook.mjs deleted file mode 100644 index 05f359f2b57f..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-requestHook.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import postgres from 'postgres'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; - -const sql = postgres({ port: 5446, user: 'test', password: 'test', database: 'test_db' }); - -async function run() { - await Sentry.startSpan( - { - name: 'Test Transaction', - op: 'transaction', - }, - async () => { - try { - await waitForConnection(() => sql`SELECT 1`); - 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")); - `; - - const email = `${uuid4()}@domain.com`; - await sql` - INSERT INTO "User" ("email", "name") VALUES (${email}, 'tim'); - `; - - await sql` - SELECT * FROM "User" WHERE "email" = ${email}; - `; - } finally { - await sql` - DROP TABLE "User"; - `; - await sql.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-unsafe.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-unsafe.mjs deleted file mode 100644 index 7d75758804b3..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-unsafe.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import postgres from 'postgres'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; - -// Test with plain object options -const sql = postgres({ port: 5446, user: 'test', password: 'test', database: 'test_db' }); - -async function run() { - await Sentry.startSpan( - { - name: 'Test Transaction', - op: 'transaction', - }, - async () => { - try { - await waitForConnection(() => sql`SELECT 1`); - // Test sql.unsafe() - this was not being instrumented before the fix - await sql.unsafe('CREATE TABLE "User" ("id" SERIAL NOT NULL, "email" TEXT NOT NULL, PRIMARY KEY ("id"))'); - - const email = `${uuid4()}@domain.com`; - await sql.unsafe('INSERT INTO "User" ("email") VALUES ($1)', [email]); - - await sql.unsafe('SELECT * FROM "User" WHERE "email" = $1', [email]); - - await sql.unsafe('DROP TABLE "User"'); - - // This will be captured as an error as the table no longer exists - await sql.unsafe('SELECT * FROM "User"'); - } finally { - await sql.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-url.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-url.mjs deleted file mode 100644 index 34301871ce16..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario-url.mjs +++ /dev/null @@ -1,75 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import postgres from 'postgres'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; - -// Test URL-based initialization - this is the common pattern that was causing the regression -const sql = postgres('postgres://test:test@localhost:5446/test_db'); - -async function run() { - await Sentry.startSpan( - { - name: 'Test Transaction', - op: 'transaction', - }, - async () => { - try { - await waitForConnection(() => sql`SELECT 1`); - 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")); - `; - - const email = `${uuid4()}@domain.com`; - await sql` - INSERT INTO "User" ("email", "name") VALUES (${email}, 'tim'); - `; - - await sql` - UPDATE "User" SET "name" = 'Foo' WHERE "email" = ${email}; - `; - - await sql` - SELECT * FROM "User" WHERE "email" = ${email}; - `; - - // Test parameterized queries - await sql` - SELECT * FROM "User" WHERE "email" = ${email} AND "name" = ${'Foo'}; - `; - - // Test DELETE operation - await sql` - DELETE FROM "User" WHERE "email" = ${email}; - `; - - // Test INSERT with RETURNING - await sql` - INSERT INTO "User" ("email", "name") VALUES (${email}, 'Test User') RETURNING *; - `; - - // Test cursor-based queries - await sql`SELECT * from generate_series(1,1000) as x `.cursor(10, async rows => { - await Promise.all(rows); - }); - - // Test multiple rows at once - await sql` - SELECT * FROM "User" LIMIT 10; - `; - - await sql` - DROP TABLE "User"; - `; - - // This will be captured as an error as the table no longer exists - await sql` - SELECT * FROM "User" WHERE "email" = ${email}; - `; - } finally { - await sql.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario.mjs deleted file mode 100644 index 12dafe97300a..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/scenario.mjs +++ /dev/null @@ -1,74 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { uuid4 } from '@sentry/core'; -import postgres from 'postgres'; -import { waitForConnection } from '@sentry-internal/node-integration-tests'; - -const sql = postgres({ port: 5446, user: 'test', password: 'test', database: 'test_db' }); - -async function run() { - await Sentry.startSpan( - { - name: 'Test Transaction', - op: 'transaction', - }, - async () => { - try { - await waitForConnection(() => sql`SELECT 1`); - 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")); - `; - - const email = `${uuid4()}@domain.com`; - await sql` - INSERT INTO "User" ("email", "name") VALUES (${email}, 'tim'); - `; - - await sql` - UPDATE "User" SET "name" = 'Foo' WHERE "email" = ${email}; - `; - - await sql` - SELECT * FROM "User" WHERE "email" = ${email}; - `; - - // Test parameterized queries - await sql` - SELECT * FROM "User" WHERE "email" = ${email} AND "name" = ${'Foo'}; - `; - - // Test DELETE operation - await sql` - DELETE FROM "User" WHERE "email" = ${email}; - `; - - // Test INSERT with RETURNING - await sql` - INSERT INTO "User" ("email", "name") VALUES (${email}, 'Test User') RETURNING *; - `; - - // Test cursor-based queries - await sql`SELECT * from generate_series(1,1000) as x `.cursor(10, async rows => { - await Promise.all(rows); - }); - - // Test multiple rows at once - await sql` - SELECT * FROM "User" LIMIT 10; - `; - - await sql` - DROP TABLE "User"; - `; - - // This will be captured as an error as the table no longer exists - await sql` - SELECT * FROM "User" WHERE "email" = ${email}; - `; - } finally { - await sql.end(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/test.ts deleted file mode 100644 index 5acbaba41e8f..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs-streamed/test.ts +++ /dev/null @@ -1,325 +0,0 @@ -import type { SerializedStreamedSpanContainer } from '@sentry/core'; -import { afterAll, describe, expect } from 'vitest'; -import { RUNTIME } from '../../../utils'; -import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; - -// On Bun, `postgres` resolves to its ESM build through the `bun` export condition, so -// `require('postgres')` returns the module namespace instead of the `postgres` function. - -/** - * Streamed span attributes are `{ value, type }` objects, unlike transaction span `data`, - * which stores values directly. - */ -function attr(value: unknown): unknown { - return expect.objectContaining({ value }); -} - -/** - * The attributes every query span carries, regardless of the statement. - */ -const COMMON_DB_ATTRIBUTES = { - 'db.namespace': attr('test_db'), - 'db.system.name': attr('postgres'), - 'sentry.op': attr('db'), - 'sentry.origin': attr('auto.db.postgresjs'), - 'server.address': attr('localhost'), - 'server.port': attr(5446), -}; - -/** - * Builds the expectation for one streamed query span. - * - * `name` is asserted separately from `db.query.summary` even though the two always match: the point - * of this suite is that the span name is the summary and never the statement, so both sides of that - * equality have to be pinned. `statement` is the sanitized `db.query.text`, which does keep the full - * (parameterized) SQL. - */ -function expectedQuerySpan({ - name, - statement, - operation, - extraAttributes = {}, -}: { - name: string; - statement: string; - operation: string; - extraAttributes?: Record; -}): unknown { - return expect.objectContaining({ - name, - is_segment: false, - status: 'ok', - attributes: expect.objectContaining({ - ...COMMON_DB_ATTRIBUTES, - 'db.operation.name': attr(operation), - 'db.query.text': attr(statement), - 'db.query.summary': attr(name), - ...extraAttributes, - }), - }); -} - -const CREATE_USER_TABLE_STATEMENT = - '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"))'; - -function getDbSpans(container: SerializedStreamedSpanContainer): SerializedStreamedSpanContainer['items'] { - return container.items.filter(item => item.attributes['sentry.op']?.value === 'db'); -} - -describeWithDockerCompose('postgresjs auto instrumentation (streamed)', { workingDirectory: [__dirname] }, () => { - afterAll(() => { - cleanupChildProcesses(); - }); - - describe('basic', () => { - const EXPECTED_SPANS = { - items: expect.arrayContaining([ - expect.objectContaining({ name: 'Test Transaction', is_segment: true }), - expectedQuerySpan({ - name: 'CREATE TABLE "User"', - statement: CREATE_USER_TABLE_STATEMENT, - operation: 'CREATE TABLE', - }), - expectedQuerySpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - operation: 'INSERT', - }), - expectedQuerySpan({ - name: 'UPDATE "User"', - statement: 'UPDATE "User" SET "name" = ? WHERE "email" = $1', - operation: 'UPDATE', - }), - expectedQuerySpan({ - name: 'SELECT "User"', - statement: 'SELECT * FROM "User" WHERE "email" = $1', - operation: 'SELECT', - }), - // Parameterized query test - verifies that tagged template queries with interpolations - // are properly reconstructed with $1, $2 placeholders which are PRESERVED per OTEL spec - // (PostgreSQL $n placeholders indicate parameterized queries that don't leak sensitive data) - expectedQuerySpan({ - name: 'SELECT "User"', - statement: 'SELECT * FROM "User" WHERE "email" = $1 AND "name" = $2', - operation: 'SELECT', - }), - expectedQuerySpan({ - name: 'DELETE "User"', - statement: 'DELETE FROM "User" WHERE "email" = $1', - operation: 'DELETE', - }), - expectedQuerySpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?) RETURNING *', - operation: 'INSERT', - }), - // The cursor query summarizes to the set-returning function it selects from. - expectedQuerySpan({ - name: 'SELECT generate_series', - statement: 'SELECT * from generate_series(?,?) as x', - operation: 'SELECT', - }), - expectedQuerySpan({ - name: 'DROP TABLE "User"', - statement: 'DROP TABLE "User"', - operation: 'DROP TABLE', - }), - // The table is gone by now, so this one fails. - expect.objectContaining({ - name: 'SELECT "User"', - is_segment: false, - status: 'error', - attributes: expect.objectContaining({ - ...COMMON_DB_ATTRIBUTES, - 'db.operation.name': attr('SELECT'), - 'db.query.text': attr('SELECT * FROM "User" WHERE "email" = $1'), - 'db.query.summary': attr('SELECT "User"'), - 'db.response.status_code': attr('42P01'), - 'error.type': attr('PostgresError'), - 'sentry.status.message': attr('relation "User" does not exist'), - }), - }), - ]), - }; - - const EXPECTED_ERROR_EVENT = { - event_id: expect.any(String), - contexts: { - trace: { - trace_id: expect.any(String), - span_id: expect.any(String), - }, - }, - exception: { - values: [ - { - type: 'PostgresError', - value: 'relation "User" does not exist', - stacktrace: expect.objectContaining({ - frames: expect.arrayContaining([ - expect.objectContaining({ - function: 'handle', - // Module differs between CJS (`postgres.cjs.src:connection`) and ESM (`postgres.src:connection`) - module: expect.stringMatching(/^postgres(\.cjs)?\.src:connection$/), - filename: expect.any(String), - lineno: expect.any(Number), - colno: expect.any(Number), - }), - ]), - }), - }, - ], - }, - }; - - createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test, mode) => { - test.skipIf(RUNTIME === 'bun' && mode === 'cjs')( - 'should auto-instrument `postgres` package', - { timeout: 90_000 }, - async () => { - await createTestRunner() - .expect({ - span: container => { - expect(container).toMatchObject(EXPECTED_SPANS); - - // The assertions above only cover the queries the scenario issues itself. postgres.js - // also runs internal ones (e.g. the `pg_catalog` type lookup), so guard the invariant - // across every query span: the name is the summary, never the statement. - const dbSpans = getDbSpans(container); - expect(dbSpans.length).toBeGreaterThan(0); - for (const span of dbSpans) { - expect(span.name).toBe(span.attributes['db.query.summary']?.value); - } - }, - }) - .expect({ event: EXPECTED_ERROR_EVENT }) - // The error event is captured via an unhandled rejection processed on a later tick than - // the spans, so the two envelopes can reach the transport in either order. - .unordered() - .start() - .completed(); - }, - ); - }); - }); - - describe('requestHook', () => { - const EXPECTED_SPANS = { - items: expect.arrayContaining( - [ - { name: 'CREATE TABLE "User"', statement: CREATE_USER_TABLE_STATEMENT, operation: 'CREATE TABLE' }, - { - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - operation: 'INSERT', - }, - { name: 'SELECT "User"', statement: 'SELECT * FROM "User" WHERE "email" = $1', operation: 'SELECT' }, - { name: 'DROP TABLE "User"', statement: 'DROP TABLE "User"', operation: 'DROP TABLE' }, - ].map(({ name, statement, operation }) => - expectedQuerySpan({ - name, - statement, - operation, - extraAttributes: { - 'custom.requestHook': attr('called'), - 'custom.requestHook.query': attr(statement), - 'custom.requestHook.database': attr('test_db'), - 'custom.requestHook.host': attr('localhost'), - 'custom.requestHook.port': attr('5446'), - }, - }), - ), - ), - }; - - createEsmAndCjsTests( - __dirname, - 'scenario-requestHook.mjs', - 'instrument-requestHook.mjs', - (createTestRunner, test, mode) => { - test.skipIf(RUNTIME === 'bun' && mode === 'cjs')( - 'should call requestHook when provided', - { timeout: 90_000 }, - async () => { - await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); - }, - ); - }, - ); - }); - - describe('url initialization', () => { - const EXPECTED_SPANS = { - items: expect.arrayContaining([ - expectedQuerySpan({ - name: 'CREATE TABLE "User"', - statement: CREATE_USER_TABLE_STATEMENT, - operation: 'CREATE TABLE', - }), - expectedQuerySpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - operation: 'INSERT', - }), - expectedQuerySpan({ - name: 'SELECT "User"', - statement: 'SELECT * FROM "User" WHERE "email" = $1', - operation: 'SELECT', - }), - expectedQuerySpan({ - name: 'DELETE "User"', - statement: 'DELETE FROM "User" WHERE "email" = $1', - operation: 'DELETE', - }), - ]), - }; - - createEsmAndCjsTests(__dirname, 'scenario-url.mjs', 'instrument.mjs', (createTestRunner, test, mode) => { - test.skipIf(RUNTIME === 'bun' && mode === 'cjs')( - 'should instrument postgres package with URL initialization', - { timeout: 90_000 }, - async () => { - await createTestRunner().ignore('event').expect({ span: EXPECTED_SPANS }).start().completed(); - }, - ); - }); - }); - - describe('sql.unsafe()', () => { - const EXPECTED_SPANS = { - items: expect.arrayContaining([ - expectedQuerySpan({ - name: 'CREATE TABLE "User"', - statement: 'CREATE TABLE "User" ("id" SERIAL NOT NULL, "email" TEXT NOT NULL, PRIMARY KEY ("id"))', - operation: 'CREATE TABLE', - }), - // sql.unsafe() with $1 placeholders - preserved per OTEL spec - expectedQuerySpan({ - name: 'INSERT "User"', - statement: 'INSERT INTO "User" ("email") VALUES ($1)', - operation: 'INSERT', - }), - expectedQuerySpan({ - name: 'SELECT "User"', - statement: 'SELECT * FROM "User" WHERE "email" = $1', - operation: 'SELECT', - }), - expectedQuerySpan({ - name: 'DROP TABLE "User"', - statement: 'DROP TABLE "User"', - operation: 'DROP TABLE', - }), - ]), - }; - - createEsmAndCjsTests(__dirname, 'scenario-unsafe.mjs', 'instrument.mjs', (createTestRunner, test, mode) => { - test.skipIf(RUNTIME === 'bun' && mode === 'cjs')( - 'should instrument sql.unsafe() queries', - { timeout: 90_000 }, - async () => { - await createTestRunner().ignore('event').expect({ span: EXPECTED_SPANS }).start().completed(); - }, - ); - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.mjs index 15cbc82e5890..51df71b79dff 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.mjs @@ -2,22 +2,18 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; const requestHook = (span, sanitizedSqlQuery, connectionContext) => { - // Add custom attributes to demonstrate requestHook functionality - span.setAttribute('custom.requestHook', 'called'); - - // Set context information as extras for test validation - Sentry.setExtra('requestHookCalled', { - sanitizedQuery: sanitizedSqlQuery, - database: connectionContext?.ATTR_DB_NAMESPACE, - host: connectionContext?.ATTR_SERVER_ADDRESS, - port: connectionContext?.ATTR_SERVER_PORT, + span.setAttributes({ + 'custom.requestHook': 'called', + 'custom.requestHook.query': sanitizedSqlQuery, + 'custom.requestHook.database': connectionContext?.ATTR_DB_NAMESPACE, + 'custom.requestHook.host': connectionContext?.ATTR_SERVER_ADDRESS, + 'custom.requestHook.port': connectionContext?.ATTR_SERVER_PORT, }); }; // `postgresJsIntegration()` is the diagnostics-channel implementation by default; it forwards the // `requestHook` to the channel subscriber. Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, 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 a0fffe40d453..d27b6f1221c8 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -1,3 +1,18 @@ +import { + DB_NAMESPACE, + DB_OPERATION_NAME, + DB_QUERY_SUMMARY, + DB_QUERY_TEXT, + DB_RESPONSE_STATUS_CODE, + DB_SYSTEM_NAME, + ERROR_TYPE, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_STATUS_MESSAGE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; +import type { Event, SerializedStreamedSpanContainer } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { RUNTIME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -10,189 +25,224 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: cleanupChildProcesses(); }); - // Under orchestrion (INJECT_ORCHESTRION), the OTel `PostgresJs` integration is - // swapped for the diagnostics-channel one, so query spans carry a different - // origin. Every other attribute is identical. const ORIGIN = 'auto.db.postgresjs'; describe('basic', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - 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': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE TABLE' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + '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]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, }), - 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', + name: 'CREATE TABLE "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - 'sentry.origin': ORIGIN, - 'sentry.op': 'db', - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'UPDATE', - 'db.query.text': 'UPDATE "User" SET "name" = ? WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'UPDATE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'UPDATE "User" SET "name" = ? WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'UPDATE "User"' }, }), - description: 'UPDATE "User" SET "name" = ? WHERE "email" = $1', - op: 'db', + name: 'UPDATE "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), // Parameterized query test - verifies that tagged template queries with interpolations // are properly reconstructed with $1, $2 placeholders which are PRESERVED per OTEL spec // (PostgreSQL $n placeholders indicate parameterized queries that don't leak sensitive data) expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1 AND "name" = $2', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1 AND "name" = $2' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1 AND "name" = $2', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - 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': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * from generate_series(?,?) as x' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT generate_series' }, }), - description: 'SELECT * from generate_series(?,?) as x', - op: 'db', + name: 'SELECT generate_series', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - 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': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, }), - description: 'DROP TABLE "User"', - op: 'db', + name: 'DROP TABLE "User"', status: 'ok', - origin: ORIGIN, parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.response.status_code': '42P01', - 'error.type': 'PostgresError', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_RESPONSE_STATUS_CODE]: { type: 'string', value: '42P01' }, + [ERROR_TYPE]: { type: 'string', value: 'PostgresError' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [SENTRY_STATUS_MESSAGE]: { type: 'string', value: 'relation "User" does not exist' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', - status: 'internal_error', - origin: ORIGIN, + name: 'SELECT "User"', + status: 'error', parent_span_id: expect.any(String), span_id: expect.any(String), start_timestamp: expect.any(Number), - timestamp: expect.any(Number), + end_timestamp: expect.any(Number), trace_id: expect.any(String), + is_segment: false, + }), + expect.objectContaining({ + name: 'DELETE "User"', + is_segment: false, + status: 'ok', + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DELETE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DELETE FROM "User" WHERE "email" = $1' }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DELETE "User"' }, + }), + }), + expect.objectContaining({ + name: 'INSERT "User"', + is_segment: false, + status: 'ok', + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?) RETURNING *', + }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + }), }), ]), }; @@ -232,102 +282,135 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: 'should auto-instrument `postgres` package', { timeout: 60_000 }, async () => { + let receivedSpans: SerializedStreamedSpanContainer['items'] = []; + let errorEvent: Event | undefined; await createTestRunner() - .expect({ transaction: EXPECTED_TRANSACTION }) - .expect({ event: EXPECTED_ERROR_EVENT }) + .expect({ + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('Test Transaction'); + receivedSpans = container.items; + }, + }) + .expect({ + event: event => { + errorEvent = event; + }, + }) // The error event is captured via an unhandled rejection processed on a later tick than - // the transaction, so the two envelopes can reach the transport in either order. + // the spans, so the two envelopes can reach the transport in either order. .unordered() .start() .completed(); + expect({ items: receivedSpans }).toMatchObject(EXPECTED_SPANS); + for (const span of receivedSpans.filter(span => span.attributes[SENTRY_OP]?.value === 'db')) { + expect(span.name).toBe(span.attributes[DB_QUERY_SUMMARY]?.value); + } + expect(errorEvent).toMatchObject(EXPECTED_ERROR_EVENT); }, ); }); }); describe('requestHook', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - 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"))', - 'custom.requestHook': 'called', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE TABLE' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + '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"))', + }, + 'custom.requestHook': { type: 'string', value: 'called' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, + 'custom.requestHook.query': { + type: 'string', + value: + '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"))', + }, + 'custom.requestHook.database': { type: 'string', value: 'test_db' }, + 'custom.requestHook.host': { type: 'string', value: 'localhost' }, + 'custom.requestHook.port': { type: 'string', value: '5444' }, }), - 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', + name: 'CREATE TABLE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - 'custom.requestHook': 'called', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, + 'custom.requestHook': { type: 'string', value: 'called' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + 'custom.requestHook.query': { + type: 'string', + value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', + }, + 'custom.requestHook.database': { type: 'string', value: 'test_db' }, + 'custom.requestHook.host': { type: 'string', value: 'localhost' }, + 'custom.requestHook.port': { type: 'string', value: '5444' }, }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'custom.requestHook': 'called', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + 'custom.requestHook': { type: 'string', value: 'called' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + 'custom.requestHook.query': { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + 'custom.requestHook.database': { type: 'string', value: 'test_db' }, + 'custom.requestHook.host': { type: 'string', value: 'localhost' }, + 'custom.requestHook.port': { type: 'string', value: '5444' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'DROP TABLE', - 'db.query.text': 'DROP TABLE "User"', - 'custom.requestHook': 'called', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, + 'custom.requestHook': { type: 'string', value: 'called' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, + 'custom.requestHook.query': { type: 'string', value: 'DROP TABLE "User"' }, + 'custom.requestHook.database': { type: 'string', value: 'test_db' }, + 'custom.requestHook.host': { type: 'string', value: 'localhost' }, + 'custom.requestHook.port': { type: 'string', value: '5444' }, }), - description: 'DROP TABLE "User"', - op: 'db', + name: 'DROP TABLE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), ]), - extra: expect.objectContaining({ - requestHookCalled: expect.objectContaining({ - database: 'test_db', - host: 'localhost', - port: '5444', - sanitizedQuery: expect.any(String), - }), - }), }; createEsmAndCjsTests( @@ -339,7 +422,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: 'should call requestHook when provided', { timeout: 60_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }, ); }, @@ -347,74 +430,76 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: }); describe('url initialization', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - 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': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE TABLE' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: + '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]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, }), - 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', + name: 'CREATE TABLE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), - description: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'DELETE', - 'db.query.text': 'DELETE FROM "User" WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DELETE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DELETE FROM "User" WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DELETE "User"' }, }), - description: 'DELETE FROM "User" WHERE "email" = $1', - op: 'db', + name: 'DELETE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), ]), }; @@ -424,80 +509,83 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: 'should instrument postgres package with URL initialization', { timeout: 90_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().ignore('event').expect({ span: EXPECTED_SPANS }).start().completed(); }, ); }); }); describe('sql.unsafe()', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - 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, "email" TEXT NOT NULL, PRIMARY KEY ("id"))', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE TABLE' }, + [DB_QUERY_TEXT]: { + type: 'string', + value: 'CREATE TABLE "User" ("id" SERIAL NOT NULL, "email" TEXT NOT NULL, PRIMARY KEY ("id"))', + }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, }), - description: 'CREATE TABLE "User" ("id" SERIAL NOT NULL, "email" TEXT NOT NULL, PRIMARY KEY ("id"))', - op: 'db', + name: 'CREATE TABLE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), // sql.unsafe() with $1 placeholders - preserved per OTEL spec expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'INSERT', - 'db.query.text': 'INSERT INTO "User" ("email") VALUES ($1)', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email") VALUES ($1)' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), - description: 'INSERT INTO "User" ("email") VALUES ($1)', - op: 'db', + name: 'INSERT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - data: expect.objectContaining({ - 'db.namespace': 'test_db', - 'db.system.name': 'postgres', - 'db.operation.name': 'SELECT', - 'db.query.text': 'SELECT * FROM "User" WHERE "email" = $1', - 'sentry.op': 'db', - 'sentry.origin': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), - description: 'SELECT * FROM "User" WHERE "email" = $1', - op: 'db', + name: 'SELECT "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), expect.objectContaining({ - 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': ORIGIN, - 'server.address': 'localhost', - 'server.port': 5444, + attributes: expect.objectContaining({ + [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, + [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, + [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 5444 }, + [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, }), - description: 'DROP TABLE "User"', - op: 'db', + name: 'DROP TABLE "User"', status: 'ok', - origin: ORIGIN, + is_segment: false, }), ]), }; @@ -509,7 +597,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: async () => { // The last query fails on purpose, and its unhandled rejection also sends an error event, which can // arrive before the transaction. - await createTestRunner().ignore('event').expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createTestRunner().ignore('event').expect({ span: EXPECTED_SPANS }).start().completed(); }, ); }); From 99216267faee303668ab785a1803bee6fc63b035 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 11:04:45 +0200 Subject: [PATCH 2/6] test(node): Use conventions constants for span ops Co-Authored-By: GPT-6 --- .../suites/tracing/postgres/test.ts | 59 ++++++++++--------- .../suites/tracing/postgresjs/test.ts | 47 +++++++-------- 2 files changed, 54 insertions(+), 52 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts index a201cd95d1d2..09b112010844 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts @@ -19,6 +19,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { DB } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { conditionalTest, EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -33,7 +34,7 @@ const COMMON_DB_ATTRIBUTES = { [SERVER_PORT]: { type: 'integer', value: expect.any(Number) }, [SENTRY_KIND]: { type: 'string', value: 'client' }, [SENTRY_ENVIRONMENT]: { type: 'string', value: 'production' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_RELEASE]: { type: 'string', value: '1.0' }, [SENTRY_SDK_NAME]: { type: 'string', value: EXPECTED_SDK_NAME }, [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, @@ -71,7 +72,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, }, name: 'pg.connect', status: 'ok', @@ -84,7 +85,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }, name: 'INSERT "User"', @@ -98,7 +99,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }, name: 'SELECT "User"', @@ -111,7 +112,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, 'db.postgresql.plan': { type: 'string', value: 'select-user-by-email' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), name: 'SELECT "User"', @@ -123,7 +124,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "does_not_exist_table"' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, }), name: 'SELECT "does_not_exist_table"', @@ -166,7 +167,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ .expect({ span: container => { expect(container).toMatchObject(EXPECTED_SPANS); - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(7); + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(7); }, }) .start() @@ -181,7 +182,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ await createTestRunner() .expect({ span: container => { - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(6); + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(6); const spanNames = container.items.map(span => span.name); expect(spanNames?.find(name => name?.includes('connect'))).toBeUndefined(); expect(container).toMatchObject({ @@ -198,7 +199,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }, name: 'INSERT "User"', @@ -212,7 +213,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }, name: 'SELECT "User"', @@ -267,7 +268,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_CONNECTION_STRING]: { type: 'string', value: 'postgresql://localhost:5494/tests' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), name: 'pg-pool.connect', @@ -280,7 +281,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS foo' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', @@ -308,7 +309,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ attributes: expect.objectContaining({ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), name: 'pg.connect', @@ -339,7 +340,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS connect_then' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', @@ -377,7 +378,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS parented' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', @@ -406,7 +407,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, [DB_CONNECTION_STRING]: { type: 'string', @@ -424,7 +425,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "NativeUser" ("email", "name") VALUES ($1, $2)' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "NativeUser"' }, [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, [DB_CONNECTION_STRING]: { @@ -443,7 +444,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "NativeUser"' }, [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "NativeUser"' }, [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, [DB_CONNECTION_STRING]: { @@ -503,7 +504,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ .expect({ span: container => { expect(container).toMatchObject(EXPECTED_SPANS); - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === 'db')).toHaveLength(5); + expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(5); }, }) .start() @@ -530,7 +531,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, }), name: 'pg.connect', status: 'ok', @@ -541,7 +542,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), name: 'INSERT "User"', @@ -554,7 +555,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, 'db.postgresql.plan': { type: 'string', value: 'select-user-by-email' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), name: 'SELECT "User"', @@ -565,7 +566,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "does_not_exist_table"' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, }), name: 'SELECT "does_not_exist_table"', @@ -590,7 +591,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_CONNECTION_STRING]: { type: 'string', value: 'postgresql://localhost:5494/tests' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), name: 'pg-pool.connect', @@ -602,7 +603,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS foo' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', @@ -626,7 +627,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ attributes: expect.objectContaining({ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_NAMESPACE]: { type: 'string', value: 'tests' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual' }, }), name: 'pg.connect', @@ -673,7 +674,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_SYSTEM_NAME]: { type: 'string', value: 'postgresql' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT ? AS parented' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', @@ -720,7 +721,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ value: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, }), name: 'INSERT "User"', @@ -732,7 +733,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [DB_NAMESPACE]: { type: 'string', value: 'tests' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User"' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, }), name: 'SELECT "User"', 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 d27b6f1221c8..f405115d961e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -12,6 +12,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { DB } from '@sentry/conventions/op'; import type { Event, SerializedStreamedSpanContainer } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { RUNTIME } from '../../../utils'; @@ -41,7 +42,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: value: '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]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -63,7 +64,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, @@ -83,7 +84,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'UPDATE' }, [DB_QUERY_TEXT]: { type: 'string', value: 'UPDATE "User" SET "name" = ? WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -104,7 +105,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -128,7 +129,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1 AND "name" = $2' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -149,7 +150,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * from generate_series(?,?) as x' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -170,7 +171,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -193,7 +194,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_RESPONSE_STATUS_CODE]: { type: 'string', value: '42P01' }, [ERROR_TYPE]: { type: 'string', value: 'PostgresError' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -216,7 +217,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: attributes: expect.objectContaining({ [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -232,7 +233,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: attributes: expect.objectContaining({ [DB_NAMESPACE]: { type: 'string', value: 'test_db' }, [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -302,7 +303,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: .start() .completed(); expect({ items: receivedSpans }).toMatchObject(EXPECTED_SPANS); - for (const span of receivedSpans.filter(span => span.attributes[SENTRY_OP]?.value === 'db')) { + for (const span of receivedSpans.filter(span => span.attributes[SENTRY_OP]?.value === DB)) { expect(span.name).toBe(span.attributes[DB_QUERY_SUMMARY]?.value); } expect(errorEvent).toMatchObject(EXPECTED_ERROR_EVENT); @@ -326,7 +327,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: '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"))', }, 'custom.requestHook': { type: 'string', value: 'called' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -351,7 +352,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, 'custom.requestHook': { type: 'string', value: 'called' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -375,7 +376,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, 'custom.requestHook': { type: 'string', value: 'called' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -396,7 +397,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, 'custom.requestHook': { type: 'string', value: 'called' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -443,7 +444,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: value: '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]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -459,7 +460,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email", "name") VALUES ($1, ?)' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -475,7 +476,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -491,7 +492,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'DELETE' }, [DB_QUERY_TEXT]: { type: 'string', value: 'DELETE FROM "User" WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -528,7 +529,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: type: 'string', value: 'CREATE TABLE "User" ("id" SERIAL NOT NULL, "email" TEXT NOT NULL, PRIMARY KEY ("id"))', }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -545,7 +546,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'INSERT INTO "User" ("email") VALUES ($1)' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -561,7 +562,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [DB_QUERY_TEXT]: { type: 'string', value: 'SELECT * FROM "User" WHERE "email" = $1' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, @@ -577,7 +578,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: [DB_SYSTEM_NAME]: { type: 'string', value: 'postgres' }, [DB_OPERATION_NAME]: { type: 'string', value: 'DROP TABLE' }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, - [SENTRY_OP]: { type: 'string', value: 'db' }, + [SENTRY_OP]: { type: 'string', value: DB }, [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, [SERVER_PORT]: { type: 'integer', value: 5444 }, From 1db56f2df690ab153141201ff3f4c91af3488dfb Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 14:50:31 +0200 Subject: [PATCH 3/6] test(node): check Postgres.js query summary attribute type Co-Authored-By: GPT-6 --- .../node-integration-tests/suites/tracing/postgresjs/test.ts | 2 +- 1 file changed, 1 insertion(+), 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 f405115d961e..d769b69d1513 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -304,7 +304,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: .completed(); expect({ items: receivedSpans }).toMatchObject(EXPECTED_SPANS); for (const span of receivedSpans.filter(span => span.attributes[SENTRY_OP]?.value === DB)) { - expect(span.name).toBe(span.attributes[DB_QUERY_SUMMARY]?.value); + expect(span.attributes[DB_QUERY_SUMMARY]).toEqual({ type: 'string', value: span.name }); } expect(errorEvent).toMatchObject(EXPECTED_ERROR_EVENT); }, From 16d71d9dfd27d4386ce661e1c0dfc3ca5413f3f3 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 16:22:16 +0200 Subject: [PATCH 4/6] test: update Node bundle size limits Co-Authored-By: GPT-6 --- .size-limit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index fcb14bb45828..0651948ecc4d 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -419,7 +419,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '150 KB', + limit: '151 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -465,7 +465,7 @@ module.exports = [ path: 'packages/node/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '128 KB', + limit: '129 KB', disablePlugins: ['@size-limit/esbuild'], ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { From 2e466d890dbb1c8c7eee2fe4e4063a929d146239 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 16:26:39 +0200 Subject: [PATCH 5/6] test(node): preserve Postgres span ordering assertions Co-Authored-By: GPT-6 --- .../suites/tracing/postgres/test.ts | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts index 09b112010844..0d4fa1212fc8 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts @@ -167,7 +167,16 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ .expect({ span: container => { expect(container).toMatchObject(EXPECTED_SPANS); - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(7); + const dbSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB); + expect(dbSpans.map(span => span.name)).toEqual([ + 'pg.connect', + 'CREATE TABLE "User"', + 'INSERT "User"', + 'SELECT "User"', + 'SELECT "User"', + 'SELECT "does_not_exist_table"', + 'DROP TABLE "User"', + ]); }, }) .start() @@ -182,7 +191,15 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ await createTestRunner() .expect({ span: container => { - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(6); + const dbSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB); + expect(dbSpans.map(span => span.name)).toEqual([ + 'CREATE TABLE "User"', + 'INSERT "User"', + 'SELECT "User"', + 'SELECT "User"', + 'SELECT "does_not_exist_table"', + 'DROP TABLE "User"', + ]); const spanNames = container.items.map(span => span.name); expect(spanNames?.find(name => name?.includes('connect'))).toBeUndefined(); expect(container).toMatchObject({ @@ -504,7 +521,14 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ .expect({ span: container => { expect(container).toMatchObject(EXPECTED_SPANS); - expect(container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB)).toHaveLength(5); + const dbSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === DB); + expect(dbSpans.map(span => span.name)).toEqual([ + 'pg.connect', + 'CREATE TABLE "NativeUser"', + 'INSERT "NativeUser"', + 'SELECT "NativeUser"', + 'DROP TABLE "NativeUser"', + ]); }, }) .start() From 0c2bf69efa232848b0f7ea600ea91fe2ec27d3b8 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 9 Oct 2026 10:15:47 +0200 Subject: [PATCH 6/6] test(node): Remove redundant Postgres stacktrace matcher Co-Authored-By: GPT-6 --- .../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 d769b69d1513..9677c10f2c7b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts @@ -261,7 +261,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: { type: 'PostgresError', value: 'relation "User" does not exist', - stacktrace: expect.objectContaining({ + stacktrace: { frames: expect.arrayContaining([ expect.objectContaining({ function: 'handle', @@ -272,7 +272,7 @@ describeWithDockerCompose('postgresjs auto instrumentation', { workingDirectory: colno: expect.any(Number), }), ]), - }), + }, }, ], },