diff --git a/dev-packages/cloudflare-integration-tests/suites/prisma/test.ts b/dev-packages/cloudflare-integration-tests/suites/prisma/test.ts index 586792d669c5..6cbba42facb4 100644 --- a/dev-packages/cloudflare-integration-tests/suites/prisma/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/prisma/test.ts @@ -55,9 +55,9 @@ it('captures a transaction with Prisma spans for a D1 query via the @sentry/clou origin: 'auto.db.cloudflare.d1', }, { - description: expect.stringMatching( - /^SELECT `main`\.`User`\.`id`, `main`\.`User`\.`email`, `main`\.`User`\.`name` FROM `main`\.`User` WHERE 1=1 LIMIT \? OFFSET \? \/\* traceparent='00-[\da-f]{32}-[\da-f]{16}-01' \*\/$/, - ), + // The sanitizer strips the D1 adapter's traceparent comment and replaces the literals. + description: + 'SELECT `main`.`User`.`id`, `main`.`User`.`email`, `main`.`User`.`name` FROM `main`.`User` WHERE ?=? LIMIT ? OFFSET ?', op: 'db.query', origin: 'auto.db.cloudflare.d1', }, diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts index c21638cf667a..dd9c526ac32e 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts @@ -89,9 +89,9 @@ Deno.test('mysql instrumentation: orchestrion:mysql:query channel produces a nes const mysqlSpan = parent.spans?.find(s => s.op === 'db'); assertExists(mysqlSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); - assertEquals(mysqlSpan!.description, 'SELECT 1 AS solution'); + assertEquals(mysqlSpan!.description, 'SELECT ? AS solution'); assertEquals(mysqlSpan!.data?.['db.system.name'], 'mysql'); - assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT 1 AS solution'); + assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT ? AS solution'); assertEquals(mysqlSpan!.data?.['server.address'], '127.0.0.1'); assertEquals(mysqlSpan!.data?.['server.port'], 3306); assertEquals(mysqlSpan!.data?.['db.user'], 'root'); diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-mysql2/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-mysql2/test.ts index afafeb6ea7f8..702baa6f175f 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-mysql2/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-mysql2/test.ts @@ -50,9 +50,9 @@ Deno.test('mysql2 instrumentation: orchestrion:mysql2:query channel produces a n const mysqlSpan = parent.spans?.find(s => s.op === 'db'); assertExists(mysqlSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); - assertEquals(mysqlSpan!.description, 'SELECT 1 AS solution'); + assertEquals(mysqlSpan!.description, 'SELECT ? AS solution'); assertEquals(mysqlSpan!.data?.['db.system.name'], 'mysql'); - assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT 1 AS solution'); + assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT ? AS solution'); assertEquals(mysqlSpan!.data?.['db.namespace'], 'mydb'); assertEquals(mysqlSpan!.data?.['db.user'], 'root'); assertEquals(mysqlSpan!.data?.['server.address'], '127.0.0.1'); diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts index 2e7ca65e4d69..2880f7c490dd 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts @@ -89,9 +89,9 @@ Deno.test('pg instrumentation: orchestrion:pg:query channel produces a nested db const pgSpan = parent.spans?.find(s => s.op === 'db'); assertExists(pgSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); - assertEquals(pgSpan!.description, 'SELECT 1 AS solution'); + assertEquals(pgSpan!.description, 'SELECT ? AS solution'); assertEquals(pgSpan!.data?.['db.system.name'], 'postgresql'); - assertEquals(pgSpan!.data?.['db.query.text'], 'SELECT 1 AS solution'); + assertEquals(pgSpan!.data?.['db.query.text'], 'SELECT ? AS solution'); assertEquals(pgSpan!.data?.['server.address'], '127.0.0.1'); assertEquals(pgSpan!.data?.['server.port'], 5432); assertEquals(pgSpan!.data?.['db.user'], 'root'); diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-tedious/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-tedious/test.ts index 9cb6c45468bc..4bb448797cad 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-tedious/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-tedious/test.ts @@ -53,11 +53,11 @@ Deno.test('tedious instrumentation: orchestrion:tedious:execSql channel produces const tediousSpan = parent.spans?.find(s => s.op === 'db'); assertExists(tediousSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`); - assertEquals(tediousSpan!.description, 'SELECT 1'); + assertEquals(tediousSpan!.description, 'SELECT ?'); assertEquals(tediousSpan!.data?.['db.system.name'], 'mssql'); assertEquals(tediousSpan!.data?.['db.namespace'], 'mydb'); assertEquals(tediousSpan!.data?.['db.user'], 'sa'); - assertEquals(tediousSpan!.data?.['db.query.text'], 'SELECT 1'); + assertEquals(tediousSpan!.data?.['db.query.text'], 'SELECT ?'); assertEquals(tediousSpan!.data?.['server.address'], '127.0.0.1'); assertEquals(tediousSpan!.data?.['server.port'], 1433); assertEquals(tediousSpan!.data?.['sentry.origin'], 'auto.db.tedious'); diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/db.test.ts index d4219abfe827..8793c5a17cd9 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/db.test.ts @@ -12,14 +12,14 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); // With span streaming the span name is the low-cardinality query summary; the statement stays in // `db.query.text`. expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql'); expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql'); - expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT 1 + 1 AS solution'); + expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT ? + ? AS solution'); expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1'); expect(firstQuery!.attributes['server.port']?.value).toBe(3306); expect(firstQuery!.attributes['db.user']?.value).toBe('root'); @@ -35,6 +35,6 @@ test('a nested query lands on the same trace (async context restored)', async ({ const queryTexts = spans .filter(span => getSpanOp(span) === 'db') .map(span => span.attributes['db.query.text']?.value); - expect(queryTexts).toContain('SELECT 1 + 1 AS solution'); + expect(queryTexts).toContain('SELECT ? + ? AS solution'); expect(queryTexts).toContain('SELECT NOW()'); }); diff --git a/dev-packages/e2e-tests/test-applications/astro-7-static/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/db.test.ts index ffaf9124bcb2..21436c72e7f0 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7-static/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7-static/tests/db.test.ts @@ -58,11 +58,11 @@ test('Instruments mysql automatically', async ({ baseURL }) => { expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts index b1a8a475c268..06d3457ea781 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts @@ -62,6 +62,6 @@ test('Instruments mysql automatically', async ({ baseURL }) => { }), }); - expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT 1 + 1 AS solution')); + expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT ? + ? AS solution')); expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT NOW()')); }); diff --git a/dev-packages/e2e-tests/test-applications/bun-mysql/tests/mysql.test.ts b/dev-packages/e2e-tests/test-applications/bun-mysql/tests/mysql.test.ts index 8437061fd1bd..643d71ef6e8d 100644 --- a/dev-packages/e2e-tests/test-applications/bun-mysql/tests/mysql.test.ts +++ b/dev-packages/e2e-tests/test-applications/bun-mysql/tests/mysql.test.ts @@ -25,14 +25,14 @@ test('mysql queries emit a db span with orchestrion-channel attributes', async ( const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); // With span streaming, db span names are the low-cardinality query summary, not the raw SQL expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes).toMatchObject({ 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, 'db.system.name': { value: 'mysql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, 'server.port': { value: 3306, type: 'integer' }, 'db.user': { value: 'root', type: 'string' }, }); @@ -58,7 +58,7 @@ test('a nested query lands on the same trace (async context restored)', async ({ const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); const queries = dbSpans.map(span => span.attributes['db.query.text']?.value); - expect(queries).toContain('SELECT 1 + 1 AS solution'); + expect(queries).toContain('SELECT ? + ? AS solution'); expect(queries).toContain('SELECT NOW()'); expect(dbSpans.every(span => span.parent_span_id === segment.span_id)).toBe(true); }); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tests/mysql.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tests/mysql.test.ts index aff772dc73ed..c5ae3b42aa40 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tests/mysql.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tests/mysql.test.ts @@ -23,13 +23,13 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); // With span streaming the span name is the low-cardinality query summary; the statement stays in `db.query.text`. expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql'); expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql'); - expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT 1 + 1 AS solution'); + expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT ? + ? AS solution'); expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1'); expect(firstQuery!.attributes['server.port']?.value).toBe(3306); expect(firstQuery!.attributes['db.user']?.value).toBe('root'); @@ -58,6 +58,6 @@ test('a nested query lands on the same trace (async context restored)', async ({ const queryTexts = spans .filter(span => getSpanOp(span) === 'db') .map(span => span.attributes['db.query.text']?.value); - expect(queryTexts).toContain('SELECT 1 + 1 AS solution'); + expect(queryTexts).toContain('SELECT ? + ? AS solution'); expect(queryTexts).toContain('SELECT NOW()'); }); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/tests/db.test.ts index 7e58fbb59577..be8068b2958c 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/tests/db.test.ts @@ -62,11 +62,11 @@ test.describe('orchestrion DB instrumentation', () => { expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/db.test.ts index 2fae830957cd..aa7d75a20127 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/tests/db.test.ts @@ -53,7 +53,7 @@ test.describe('orchestrion DB instrumentation', () => { // With span streaming the span name is the low-cardinality query summary; the statement // stays in `db.query.text`. - for (const query of ['SELECT 1 + 1 AS solution', 'SELECT NOW()']) { + for (const query of ['SELECT ? + ? AS solution', 'SELECT NOW()']) { expect(mysqlSpans).toContainEqual( expect.objectContaining({ name: 'SELECT', diff --git a/dev-packages/e2e-tests/test-applications/deno-mysql/tests/mysql.test.ts b/dev-packages/e2e-tests/test-applications/deno-mysql/tests/mysql.test.ts index 044336f28ae1..0ac07ed48870 100644 --- a/dev-packages/e2e-tests/test-applications/deno-mysql/tests/mysql.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno-mysql/tests/mysql.test.ts @@ -24,14 +24,14 @@ test('mysql queries emit a db span with orchestrion-channel attributes', async ( const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); // With span streaming, db span names are the low-cardinality query summary, not the raw SQL expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes).toMatchObject({ 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, 'db.system.name': { value: 'mysql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, 'server.port': { value: 3306, type: 'integer' }, 'db.user': { value: 'root', type: 'string' }, }); @@ -57,7 +57,7 @@ test('a nested query lands on the same trace (AsyncLocalStorage context restored const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); const queries = dbSpans.map(span => span.attributes['db.query.text']?.value); - expect(queries).toContain('SELECT 1 + 1 AS solution'); + expect(queries).toContain('SELECT ? + ? AS solution'); expect(queries).toContain('SELECT NOW()'); expect(dbSpans.every(span => span.parent_span_id === segment.span_id)).toBe(true); }); diff --git a/dev-packages/e2e-tests/test-applications/deno-pg/tests/pg.test.ts b/dev-packages/e2e-tests/test-applications/deno-pg/tests/pg.test.ts index 48b07cb5b80e..32f24dcb43a4 100644 --- a/dev-packages/e2e-tests/test-applications/deno-pg/tests/pg.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno-pg/tests/pg.test.ts @@ -24,14 +24,14 @@ test('pg queries emit a db span with orchestrion-channel attributes', async ({ b const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); // With span streaming, db span names are the low-cardinality query summary, not the raw SQL expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes).toMatchObject({ 'sentry.origin': { value: 'auto.db.postgres', type: 'string' }, 'db.system.name': { value: 'postgresql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, 'server.port': { value: 5432, type: 'integer' }, 'db.user': { value: 'postgres', type: 'string' }, }); @@ -58,7 +58,7 @@ test('a nested query lands on the same trace (AsyncLocalStorage context restored const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); const queries = dbSpans.map(span => span.attributes['db.query.text']?.value); - expect(queries).toContain('SELECT 1 + 1 AS solution'); + expect(queries).toContain('SELECT ? + ? AS solution'); expect(queries).toContain('SELECT NOW()'); expect(dbSpans.every(span => span.parent_span_id === segment.span_id)).toBe(true); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/db-page.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/db-page.test.ts index f82a49550158..2627601534de 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/db-page.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-static/tests/db-page.test.ts @@ -20,11 +20,11 @@ test('Instruments DB calls made during server-side rendering of a page', async ( expect.objectContaining({ op: 'db', origin: 'auto.db.postgres', - description: 'SELECT 40 + 2 AS answer', + description: 'SELECT ? + ? AS answer', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'postgresql', - 'db.query.text': 'SELECT 40 + 2 AS answer', + 'db.query.text': 'SELECT ? + ? AS answer', }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts index 30392d2bf741..968891a085c4 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/db-page.test.ts @@ -21,7 +21,7 @@ test('Instruments DB calls made during server-side rendering of a page', async ( 'sentry.op': { value: 'db', type: 'string' }, 'sentry.origin': { value: 'auto.db.postgres', type: 'string' }, 'db.system.name': { value: 'postgresql', type: 'string' }, - 'db.query.text': { value: 'SELECT 40 + 2 AS answer', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS answer', type: 'string' }, }), }), ); diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts index bfef44b87809..cc7ffd05e0b5 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts @@ -154,7 +154,7 @@ test('Instruments MySQL via Orchestrion', async ({ baseURL }) => { attributes: expect.objectContaining({ 'sentry.op': { value: 'db', type: 'string' }, 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, }), }), expect.objectContaining({ diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/mysql.test.ts b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/mysql.test.ts index 3bd6ea267256..90a20f331ac7 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/tests/mysql.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/tests/mysql.test.ts @@ -31,7 +31,7 @@ test('Instruments MySQL via Orchestrion', async ({ baseURL }) => { attributes: expect.objectContaining({ 'sentry.op': { value: 'db', type: 'string' }, 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, }), }), expect.objectContaining({ diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts index 5c58b6c1a165..c5810a56cbe8 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/tests/database.test.ts @@ -109,7 +109,7 @@ test.describe('database integration', () => { expect(insertSpan).toBeDefined(); expect(insertSpan?.attributes).toMatchObject({ 'db.query.summary': { type: 'string', value: 'INSERT logs' }, - 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')` }, + 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES (?, ?)` }, 'db.system.name': { type: 'string', value: 'sqlite' }, 'sentry.origin': { type: 'string', value: 'auto.db.nuxt' }, }); @@ -178,8 +178,8 @@ test.describe('database integration', () => { ); expect(dbBreadcrumb).toBeDefined(); - expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); - expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); + expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); }); test('multiple database operations in single request create multiple spans', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/tests/db.test.ts index af9f1e7259d4..62bfdb837e53 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/tests/db.test.ts @@ -26,13 +26,13 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a const dbSpans = spans.filter(span => getSpanOp(span) === 'db' && span.trace_id === rootSpan!.trace_id); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes).toMatchObject({ 'sentry.origin': { type: 'string', value: 'auto.db.mysql' }, 'db.system.name': { type: 'string', value: 'mysql' }, - 'db.query.text': { type: 'string', value: 'SELECT 1 + 1 AS solution' }, + 'db.query.text': { type: 'string', value: 'SELECT ? + ? AS solution' }, 'server.address': { type: 'string', value: '127.0.0.1' }, 'server.port': { type: 'integer', value: 3306 }, 'db.user': { type: 'string', value: 'root' }, @@ -52,6 +52,6 @@ test('a nested query lands on the same trace (async context restored)', async ({ const queryTexts = spans .filter(span => getSpanOp(span) === 'db' && span.trace_id === rootSpan!.trace_id) .map(span => span.attributes['db.query.text']?.value); - expect(queryTexts).toContain('SELECT 1 + 1 AS solution'); + expect(queryTexts).toContain('SELECT ? + ? AS solution'); expect(queryTexts).toContain('SELECT NOW()'); }); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/database.test.ts index a046d2924bef..0c1878c65c3e 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/database.test.ts @@ -120,9 +120,9 @@ test.describe('database integration', () => { expect(dbSpan).toBeDefined(); expect(dbSpan?.op).toBe('db.query'); - expect(dbSpan?.description).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbSpan?.description).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); expect(dbSpan?.data?.['db.system.name']).toBe('sqlite'); - expect(dbSpan?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbSpan?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); expect(dbSpan?.data?.['sentry.origin']).toBe('auto.db.nuxt'); }); @@ -180,8 +180,8 @@ test.describe('database integration', () => { expect(dbBreadcrumb).toBeDefined(); expect(dbBreadcrumb?.category).toBe('query'); - expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); - expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); + expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); }); test('multiple database operations in single request create multiple spans', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/db-drivers.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/db-drivers.test.ts index 786612072d95..2abf802d1836 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/db-drivers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-static/tests/db-drivers.test.ts @@ -74,11 +74,11 @@ test('Instruments mysql automatically', async ({ baseURL }) => { expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts index 7f0b60244f62..b73c4ee990f4 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/database.test.ts @@ -109,7 +109,7 @@ test.describe('database integration', () => { expect(insertSpan).toBeDefined(); expect(insertSpan?.attributes).toMatchObject({ 'db.query.summary': { type: 'string', value: 'INSERT logs' }, - 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')` }, + 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES (?, ?)` }, 'db.system.name': { type: 'string', value: 'sqlite' }, 'sentry.origin': { type: 'string', value: 'auto.db.nuxt' }, }); @@ -178,8 +178,8 @@ test.describe('database integration', () => { ); expect(dbBreadcrumb).toBeDefined(); - expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); - expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); + expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); }); test('multiple database operations in single request create multiple spans', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts index 1b9c97ce38e2..a093dd9e8d67 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/db-drivers.test.ts @@ -87,7 +87,7 @@ test('Instruments mysql automatically', async ({ baseURL }) => { 'sentry.op': { type: 'string', value: 'db' }, 'sentry.origin': { type: 'string', value: 'auto.db.mysql' }, 'db.system.name': { type: 'string', value: 'mysql' }, - 'db.query.text': { type: 'string', value: 'SELECT 1 + 1 AS solution' }, + 'db.query.text': { type: 'string', value: 'SELECT ? + ? AS solution' }, 'db.query.summary': { type: 'string', value: 'SELECT' }, 'db.user': { type: 'string', value: 'root' }, 'db.connection_string': { type: 'string', value: expect.any(String) }, diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts index 2a1b16f23d5b..de55b7ce8df4 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/database.test.ts @@ -109,7 +109,7 @@ test.describe('database integration', () => { expect(insertSpan).toBeDefined(); expect(insertSpan?.attributes).toMatchObject({ 'db.query.summary': { type: 'string', value: 'INSERT logs' }, - 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')` }, + 'db.query.text': { type: 'string', value: `INSERT INTO logs (message, level) VALUES (?, ?)` }, 'db.system.name': { type: 'string', value: 'sqlite' }, 'sentry.origin': { type: 'string', value: 'auto.db.nuxt' }, }); @@ -178,8 +178,8 @@ test.describe('database integration', () => { ); expect(dbBreadcrumb).toBeDefined(); - expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); - expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`); + expect(dbBreadcrumb?.message).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); + expect(dbBreadcrumb?.data?.['db.query.text']).toBe(`INSERT INTO logs (message, level) VALUES (?, ?)`); }); test('multiple database operations in single request create multiple spans', async ({ request }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts index ed0f3e415c36..d0013168b95f 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/db.server.test.ts @@ -77,7 +77,7 @@ test.describe('server - orchestrion db instrumentation', () => { const spans = await spansPromise; - for (const queryText of ['SELECT 1 + 1 AS solution', 'SELECT NOW()']) { + for (const queryText of ['SELECT ? + ? AS solution', 'SELECT NOW()']) { expect(spans).toContainEqual( expect.objectContaining({ name: 'SELECT', diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts index ebef990b9bfc..1dfa920d1625 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-8-cloudflare/tests/db.test.ts @@ -17,7 +17,7 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a const spans = await spansPromise; const dbSpans = spans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql'); @@ -45,6 +45,6 @@ test('a nested query lands on the same segment (async context restored)', async const queryTexts = spans .filter(span => getSpanOp(span) === 'db') .map(span => span.attributes['db.query.text']?.value); - expect(queryTexts).toContain('SELECT 1 + 1 AS solution'); + expect(queryTexts).toContain('SELECT ? + ? AS solution'); expect(queryTexts).toContain('SELECT NOW()'); }); diff --git a/dev-packages/e2e-tests/test-applications/solidstart-static/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/solidstart-static/tests/db.test.ts index 3506759509db..2fa4db593d0c 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-static/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-static/tests/db.test.ts @@ -57,11 +57,11 @@ test('Instruments mysql automatically via build-time orchestrion', async ({ base expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts index 7143c25aa30f..0c546aba56f1 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/db.test.ts @@ -64,7 +64,7 @@ test('Instruments mysql automatically via build-time orchestrion', async ({ base getSpanOp(span) === 'http.server' && String(span.attributes['url.path']?.value ?? '').includes('db-mysql'), ) && - spans.some(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution') && + spans.some(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution') && spans.some(span => span.attributes['db.query.text']?.value === 'SELECT NOW()'), ); @@ -73,7 +73,7 @@ test('Instruments mysql automatically via build-time orchestrion', async ({ base const spans = await spansPromise; const mysqlSpans = spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.db.mysql'); - const firstQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = mysqlSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.status).toBe('ok'); @@ -81,7 +81,7 @@ test('Instruments mysql automatically via build-time orchestrion', async ({ base 'sentry.op': { type: 'string', value: 'db' }, 'sentry.origin': { type: 'string', value: 'auto.db.mysql' }, 'db.system.name': { type: 'string', value: 'mysql' }, - 'db.query.text': { type: 'string', value: 'SELECT 1 + 1 AS solution' }, + 'db.query.text': { type: 'string', value: 'SELECT ? + ? AS solution' }, 'db.user': { type: 'string', value: 'root' }, 'db.connection_string': { type: 'string', value: expect.any(String) }, 'server.address': { type: 'string', value: expect.any(String) }, diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/db.test.ts index 7deff11315f3..439c2133e6f2 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/tests/db.test.ts @@ -58,11 +58,11 @@ test('Instruments mysql automatically', async ({ baseURL }) => { expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/db.test.ts index a34026797147..e4a7d2d6acd2 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/db.test.ts @@ -60,6 +60,6 @@ test('Instruments mysql automatically', async ({ baseURL }) => { }), }); - expect(traceSpans).toContainEqual(mysqlSpan('SELECT 1 + 1 AS solution')); + expect(traceSpans).toContainEqual(mysqlSpan('SELECT ? + ? AS solution')); expect(traceSpans).toContainEqual(mysqlSpan('SELECT NOW()')); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/db.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/db.test.ts index 458f1212de44..e2db562af336 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/db.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/db.test.ts @@ -18,13 +18,13 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a const traceSpans = await traceSpansPromise; const dbSpans = traceSpans.filter(span => getSpanOp(span) === 'db'); - const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution'); + const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution'); expect(firstQuery).toBeDefined(); expect(firstQuery!.name).toBe('SELECT'); expect(firstQuery!.attributes).toMatchObject({ 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, 'db.system.name': { value: 'mysql', type: 'string' }, - 'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' }, + 'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' }, 'db.query.summary': { value: 'SELECT', type: 'string' }, 'server.address': { value: '127.0.0.1', type: 'string' }, 'server.port': { value: 3306, type: 'integer' }, @@ -49,7 +49,7 @@ test('a nested query lands on the same trace (async context restored)', async ({ const dbSpans = traceSpans.filter(span => getSpanOp(span) === 'db'); const queryTexts = dbSpans.map(span => span.attributes['db.query.text']?.value); - expect(queryTexts).toContain('SELECT 1 + 1 AS solution'); + expect(queryTexts).toContain('SELECT ? + ? AS solution'); expect(queryTexts).toContain('SELECT NOW()'); const parentSpanIds = new Set(dbSpans.map(span => span.parent_span_id)); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/db-drivers.test.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/db-drivers.test.ts index cbd54ffb2c96..a8cac8f5e3b6 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/db-drivers.test.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/tests/db-drivers.test.ts @@ -67,11 +67,11 @@ test('Instruments mysql automatically', async ({ baseURL }) => { expect.objectContaining({ op: 'db', origin: 'auto.db.mysql', - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', status: 'ok', data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'db.user': 'root', 'db.connection_string': expect.any(String), 'server.address': expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/mysql2/test.ts b/dev-packages/node-integration-tests/suites/tracing/knex/mysql2/test.ts index d5f615dbaaec..98cb1127906b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/knex/mysql2/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/knex/mysql2/test.ts @@ -25,7 +25,7 @@ describeWithDockerCompose('knex auto instrumentation', { workingDirectory: [__di }), status: 'ok', description: - 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(3) not null default CURRENT_TIMESTAMP(3), `email` text not null, `name` text not null)', + 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(?) not null default CURRENT_TIMESTAMP(?), `email` text not null, `name` text not null)', origin: ORIGIN, }), expect.objectContaining({ @@ -86,7 +86,7 @@ describeWithDockerCompose('knex auto instrumentation', { workingDirectory: [__di { name: 'create table `User`', summary: 'create table `User`', - text: 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(3) not null default CURRENT_TIMESTAMP(3), `email` text not null, `name` text not null)', + text: 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(?) not null default CURRENT_TIMESTAMP(?), `email` text not null, `name` text not null)', }, { name: 'insert `User`', diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/pg/test.ts b/dev-packages/node-integration-tests/suites/tracing/knex/pg/test.ts index a85a4d0250e3..5d1da5ad89ff 100644 --- a/dev-packages/node-integration-tests/suites/tracing/knex/pg/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/knex/pg/test.ts @@ -24,7 +24,7 @@ describe('knex auto instrumentation', () => { }), status: 'ok', description: - 'create table "User" ("id" serial primary key, "createdAt" timestamptz(3) not null default CURRENT_TIMESTAMP(3), "email" text not null, "name" text not null)', + 'create table "User" ("id" serial primary key, "createdAt" timestamptz(?) not null default CURRENT_TIMESTAMP(?), "email" text not null, "name" text not null)', origin: ORIGIN, }), expect.objectContaining({ @@ -100,7 +100,7 @@ describe('knex auto instrumentation', () => { { name: 'create table "User"', summary: 'create table "User"', - text: 'create table "User" ("id" serial primary key, "createdAt" timestamptz(3) not null default CURRENT_TIMESTAMP(3), "email" text not null, "name" text not null)', + text: 'create table "User" ("id" serial primary key, "createdAt" timestamptz(?) not null default CURRENT_TIMESTAMP(?), "email" text not null, "name" text not null)', }, { name: 'insert "User"', diff --git a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts index b46624189f5d..53619776fdea 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts @@ -54,7 +54,7 @@ describe('mysql auto instrumentation', () => { return { transaction: 'Test Transaction', - spans: expect.arrayContaining([span('SELECT 1 + 1 AS solution'), span('SELECT NOW()')]), + spans: expect.arrayContaining([span('SELECT ? + ? AS solution'), span('SELECT NOW()')]), ...(override ?? {}), }; } @@ -144,7 +144,7 @@ describe('mysql auto instrumentation', () => { transaction: (transaction): void => { const transactionSpanId = transaction.contexts?.trace?.span_id; const spans = transaction.spans ?? []; - const mysqlSpan = spans.find(span => span.description === 'SELECT 1 + 1 AS solution'); + const mysqlSpan = spans.find(span => span.description === 'SELECT ? + ? AS solution'); const listenerSpan = spans.find(span => span.description === 'listener-child'); const innerSpan = spans.find(span => span.description === 'inner-span'); @@ -261,7 +261,7 @@ describe('mysql auto instrumentation', () => { ...COMMON_ATTRIBUTES, 'db.query.text': { type: 'string', - value: 'SELECT 1 + 1 AS solution', + value: 'SELECT ? + ? AS solution', }, 'db.query.summary': { type: 'string', diff --git a/dev-packages/node-integration-tests/suites/tracing/mysql2/test.ts b/dev-packages/node-integration-tests/suites/tracing/mysql2/test.ts index f0fb3e4ee9a4..9733d4c7b614 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mysql2/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mysql2/test.ts @@ -14,12 +14,12 @@ describeWithDockerCompose('mysql2 auto instrumentation', { workingDirectory: [__ transaction: 'Test Transaction', spans: expect.arrayContaining([ expect.objectContaining({ - description: 'SELECT 1 + 1 AS solution', + description: 'SELECT ? + ? AS solution', op: 'db', origin: ORIGIN, data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 1 + 1 AS solution', + 'db.query.text': 'SELECT ? + ? AS solution', 'server.address': 'localhost', 'server.port': 3306, 'db.user': 'root', @@ -50,12 +50,12 @@ describeWithDockerCompose('mysql2 auto instrumentation', { workingDirectory: [__ }), // `execute` is instrumented the same way as `query` expect.objectContaining({ - description: 'SELECT 42 AS answer', + description: 'SELECT ? AS answer', op: 'db', origin: ORIGIN, data: expect.objectContaining({ 'db.system.name': 'mysql', - 'db.query.text': 'SELECT 42 AS answer', + 'db.query.text': 'SELECT ? AS answer', }), }), // a failing query produces a span with an error status 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 index 2753ef1708b7..03a3fe70fd1e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts @@ -147,10 +147,10 @@ function expectedDbSpan({ } const CREATE_USER_TABLE_STATEMENT = - '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"));'; + '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(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"));'; + '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[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'db'); 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 6907c5e1c210..ce96a147a315 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts @@ -159,11 +159,11 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ data: expect.objectContaining({ 'db.system.name': 'postgresql', 'db.namespace': 'tests', - 'db.query.text': 'SELECT 1 AS foo', + 'db.query.text': 'SELECT ? AS foo', 'sentry.origin': QUERY_ORIGIN, 'sentry.op': 'db', }), - description: 'SELECT 1 AS foo', + description: 'SELECT ? AS foo', op: 'db', status: 'ok', origin: QUERY_ORIGIN, @@ -220,11 +220,11 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ data: expect.objectContaining({ 'db.system.name': 'postgresql', 'db.namespace': 'tests', - 'db.query.text': 'SELECT 1 AS connect_then', + 'db.query.text': 'SELECT ? AS connect_then', 'sentry.origin': QUERY_ORIGIN, 'sentry.op': 'db', }), - description: 'SELECT 1 AS connect_then', + description: 'SELECT ? AS connect_then', op: 'db', status: 'ok', origin: QUERY_ORIGIN, @@ -247,7 +247,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ transaction: txn => { const descriptions = txn.spans?.map(span => span.description) ?? []; // The unparented connect + query must not have produced spans - expect(descriptions).not.toContain('SELECT 1 AS unparented'); + expect(descriptions).not.toContain('SELECT ? AS unparented'); expect(descriptions.find(name => name?.includes('connect'))).toBeUndefined(); // Only the parented query is instrumented expect(txn).toMatchObject({ @@ -257,11 +257,11 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ data: expect.objectContaining({ 'db.system.name': 'postgresql', 'db.namespace': 'tests', - 'db.query.text': 'SELECT 2 AS parented', + 'db.query.text': 'SELECT ? AS parented', 'sentry.origin': QUERY_ORIGIN, 'sentry.op': 'db', }), - description: 'SELECT 2 AS parented', + description: 'SELECT ? AS parented', op: 'db', status: 'ok', origin: QUERY_ORIGIN, @@ -424,11 +424,11 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ data: expect.objectContaining({ 'db.system.name': 'postgresql', 'db.namespace': 'tests', - 'db.query.text': 'SELECT 1 AS foo', + 'db.query.text': 'SELECT ? AS foo', 'sentry.origin': ORIGIN, 'sentry.op': 'db', }), - description: 'SELECT 1 AS foo', + description: 'SELECT ? AS foo', op: 'db', status: 'ok', origin: ORIGIN, @@ -491,7 +491,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ .expect({ transaction: txn => { const descriptions = txn.spans?.map(span => span.description) ?? []; - expect(descriptions).not.toContain('SELECT 1 AS unparented'); + expect(descriptions).not.toContain('SELECT ? AS unparented'); expect(descriptions.find(name => name?.includes('connect'))).toBeUndefined(); expect(txn).toMatchObject({ transaction: 'Test Transaction', @@ -499,11 +499,11 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ expect.objectContaining({ data: expect.objectContaining({ 'db.system.name': 'postgresql', - 'db.query.text': 'SELECT 2 AS parented', + 'db.query.text': 'SELECT ? AS parented', 'sentry.origin': ORIGIN, 'sentry.op': 'db', }), - description: 'SELECT 2 AS parented', + description: 'SELECT ? AS parented', op: 'db', status: 'ok', origin: ORIGIN, diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts index 54d63eea226c..9e9d6b7e3291 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts @@ -27,8 +27,8 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ const EXPECTED_TRANSACTION = { transaction: 'Test Transaction', spans: expect.arrayContaining([ - dbSpan({ description: 'SELECT 1 + 1 AS solution', status: 'ok' }), - dbSpan({ description: 'SELECT 42; SELECT 42;', status: 'ok' }), + dbSpan({ description: 'SELECT ? + ? AS solution', status: 'ok' }), + dbSpan({ description: 'SELECT ?; SELECT ?', status: 'ok' }), dbSpan({ description: 'select !', status: 'internal_error' }), dbSpan({ description: '[dbo].[test_proced]', status: 'ok' }), dbSpan({ description: 'INSERT INTO [dbo].[test_prepared] VALUES (@val1, @val2)', status: 'ok' }), @@ -51,27 +51,25 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ expect(transaction.spans).toEqual(EXPECTED_TRANSACTION.spans); const CREATE_PROCEDURE = - 'CREATE OR ALTER PROCEDURE [dbo].[test_proced] @inputVal varchar(30), @outputCount int OUTPUT AS set @outputCount = LEN(@inputVal);'; - const CREATE_PREPARED_TABLE = - "if object_id('[dbo].[test_prepared]') is null CREATE TABLE [dbo].[test_prepared] (c1 int, c2 int)"; - const CREATE_BULK_TABLE = - "if object_id('[dbo].[test_bulk]') is null CREATE TABLE [dbo].[test_bulk] (c1 int, c2 varchar(30))"; + 'CREATE OR ALTER PROCEDURE [dbo].[test_proced] @inputVal varchar(?), @outputCount int OUTPUT AS set @outputCount = LEN(@inputVal)'; + const CREATE_PREPARED_TABLE = 'if object_id(?) is null CREATE TABLE [dbo].[test_prepared] (c1 int, c2 int)'; + const CREATE_BULK_TABLE = 'if object_id(?) is null CREATE TABLE [dbo].[test_bulk] (c1 int, c2 varchar(?))'; const INSERT_PREPARED = 'INSERT INTO [dbo].[test_prepared] VALUES (@val1, @val2)'; - const INSERT_BULK = 'insert bulk test_bulk([c1] int, [c2] nvarchar(50)) WITH (KEEP_NULLS)'; + const INSERT_BULK = 'insert bulk test_bulk([c1] int, [c2] nvarchar(?)) WITH (KEEP_NULLS)'; const SELECT_PREPARED = 'SELECT c1, c2 FROM [dbo].[test_prepared]'; const SELECT_JOIN = 'SELECT p.c1 FROM [dbo].[test_prepared] p INNER JOIN [dbo].[test_bulk] b ON p.c1 = b.c1'; - const SELECT_INLINE_LITERAL = 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = 42'; + const SELECT_INLINE_LITERAL = 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = ?'; const SELECT_PARAMETERIZED = 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = @c1'; - const SELECT_STRING_LITERAL = "SELECT c1, c2 FROM [dbo].[test_bulk] WHERE c2 = 'hello from acme'"; + const SELECT_STRING_LITERAL = 'SELECT c1, c2 FROM [dbo].[test_bulk] WHERE c2 = ?'; expect( (transaction.spans ?? []) .filter(span => span.origin === ORIGIN) .map(span => ({ name: span.description, text: span.data?.['db.query.text'] })), ).toEqual([ - { name: 'SELECT 1 + 1 AS solution', text: 'SELECT 1 + 1 AS solution' }, - { name: 'SELECT 42; SELECT 42;', text: 'SELECT 42; SELECT 42;' }, + { name: 'SELECT ? + ? AS solution', text: 'SELECT ? + ? AS solution' }, + { name: 'SELECT ?; SELECT ?', text: 'SELECT ?; SELECT ?' }, { name: 'select !', text: 'select !' }, { name: CREATE_PROCEDURE, text: CREATE_PROCEDURE }, { name: '[dbo].[test_proced]', text: '[dbo].[test_proced]' }, @@ -108,19 +106,19 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ text: span.attributes['db.query.text']?.value, })), ).toEqual([ - { name: 'SELECT', summary: 'SELECT', text: 'SELECT 1 + 1 AS solution' }, - { name: 'SELECT', summary: 'SELECT', text: 'SELECT 42; SELECT 42;' }, + { name: 'SELECT', summary: 'SELECT', text: 'SELECT ? + ? AS solution' }, + { name: 'SELECT', summary: 'SELECT', text: 'SELECT ?; SELECT ?' }, { name: 'select', summary: 'select', text: 'select !' }, { name: 'CREATE', summary: 'CREATE', - text: 'CREATE OR ALTER PROCEDURE [dbo].[test_proced] @inputVal varchar(30), @outputCount int OUTPUT AS set @outputCount = LEN(@inputVal);', + text: 'CREATE OR ALTER PROCEDURE [dbo].[test_proced] @inputVal varchar(?), @outputCount int OUTPUT AS set @outputCount = LEN(@inputVal)', }, { name: 'callProcedure [dbo].[test_proced]', summary: undefined, text: '[dbo].[test_proced]' }, { name: 'if', summary: 'if', - text: "if object_id('[dbo].[test_prepared]') is null CREATE TABLE [dbo].[test_prepared] (c1 int, c2 int)", + text: 'if object_id(?) is null CREATE TABLE [dbo].[test_prepared] (c1 int, c2 int)', }, { name: 'INSERT [dbo].[test_prepared]', @@ -135,12 +133,12 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ { name: 'if', summary: 'if', - text: "if object_id('[dbo].[test_bulk]') is null CREATE TABLE [dbo].[test_bulk] (c1 int, c2 varchar(30))", + text: 'if object_id(?) is null CREATE TABLE [dbo].[test_bulk] (c1 int, c2 varchar(?))', }, { name: 'insert', summary: 'insert', - text: 'insert bulk test_bulk([c1] int, [c2] nvarchar(50)) WITH (KEEP_NULLS)', + text: 'insert bulk test_bulk([c1] int, [c2] nvarchar(?)) WITH (KEEP_NULLS)', }, { name: 'execBulkLoad test_bulk', summary: undefined, text: undefined }, { @@ -155,11 +153,9 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ text: 'SELECT p.c1 FROM [dbo].[test_prepared] p INNER JOIN [dbo].[test_bulk] b ON p.c1 = b.c1', }, { - // TODO: (fix) tedious reports the statement as the caller wrote it, so an inlined literal reaches - // `db.query.text` unsanitized. Only the summary is sanitized. name: 'SELECT [dbo].[test_prepared]', summary: 'SELECT [dbo].[test_prepared]', - text: 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = 42', + text: 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = ?', }, { name: 'SELECT [dbo].[test_prepared]', @@ -167,11 +163,9 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ text: 'SELECT c1, c2 FROM [dbo].[test_prepared] WHERE c1 = @c1', }, { - // TODO: (fix) The `from` inside the string literal must not be read as a table: the statement is - // sanitized before it is summarized, so the summary is just the real table. name: 'SELECT [dbo].[test_bulk]', summary: 'SELECT [dbo].[test_bulk]', - text: "SELECT c1, c2 FROM [dbo].[test_bulk] WHERE c2 = 'hello from acme'", + text: 'SELECT c1, c2 FROM [dbo].[test_bulk] WHERE c2 = ?', }, ]); }, diff --git a/packages/cloudflare/src/instrumentations/worker/instrumentD1.ts b/packages/cloudflare/src/instrumentations/worker/instrumentD1.ts index 3827a3eafdc0..147b55c573ff 100644 --- a/packages/cloudflare/src/instrumentations/worker/instrumentD1.ts +++ b/packages/cloudflare/src/instrumentations/worker/instrumentD1.ts @@ -122,7 +122,8 @@ type D1QueryType = 'first' | 'run' | 'all' | 'raw' | 'batch' | 'exec'; function createD1Breadcrumb(query: string, type: D1QueryType, d1Result?: D1Response): void { addBreadcrumb({ category: 'query', - message: query, + // The breadcrumb carries the same query text as the span, so it is sanitized the same way. + message: sanitizeSqlQuery(query), data: { ...(d1Result ? getAttributesFromD1Response(d1Result) : {}), 'db.operation.name': type, @@ -131,10 +132,11 @@ function createD1Breadcrumb(query: string, type: D1QueryType, d1Result?: D1Respo } function createStartSpanOptions(query: string, type: D1QueryType): StartSpanOptions { - const querySummary = query ? getSqlQuerySummary(sanitizeSqlQuery(query)) : undefined; + const queryText = sanitizeSqlQuery(query); + const querySummary = query ? getSqlQuerySummary(queryText) : undefined; const client = getClient(); - const name = client && hasSpanStreamingEnabled(client) ? querySummary || 'cloudflare-d1' : query; + const name = client && hasSpanStreamingEnabled(client) ? querySummary || 'cloudflare-d1' : queryText; return { name, @@ -142,7 +144,7 @@ function createStartSpanOptions(query: string, type: D1QueryType): StartSpanOpti [SENTRY_OP]: DB_QUERY, 'db.system.name': 'cloudflare-d1', 'db.operation.name': type, - 'db.query.text': query, + 'db.query.text': queryText, 'db.query.summary': querySummary, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.d1', }, diff --git a/packages/cloudflare/test/instrumentations/instrumentEnv.test.ts b/packages/cloudflare/test/instrumentations/instrumentEnv.test.ts index 70f680d52853..3e1f366e37e1 100644 --- a/packages/cloudflare/test/instrumentations/instrumentEnv.test.ts +++ b/packages/cloudflare/test/instrumentations/instrumentEnv.test.ts @@ -40,7 +40,8 @@ describe('instrumentEnv', () => { await db.prepare('SELECT 1').first(); expect(startSpanSpy).toHaveBeenCalledWith( - expect.objectContaining({ name: 'SELECT 1', attributes: expect.objectContaining({ 'sentry.op': 'db.query' }) }), + // The numeric literal is sanitized out of the query text. + expect.objectContaining({ name: 'SELECT ?', attributes: expect.objectContaining({ 'sentry.op': 'db.query' }) }), expect.any(Function), ); }); diff --git a/packages/core/src/types/datacollection.ts b/packages/core/src/types/datacollection.ts index 8815170eb04e..db4f97a82a77 100644 --- a/packages/core/src/types/datacollection.ts +++ b/packages/core/src/types/datacollection.ts @@ -79,7 +79,7 @@ export interface DataCollection { }; /** - * Include data associated with database queries. This controls collection of query parameters, inline literal values within query text, mutation/request bodies, and returned result data. + * Include data associated with database queries. This controls collection of bound query parameters, data payloads for write operations, and returned result data. * * Sanitized or parameterized DB statements (`db.query.text`) are **not** controlled by this property. Structural metadata such as the database system, query summary, operation name, or the table being acted upon is also **always** collected. * @default true diff --git a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts index 384e051fbb3b..15c0d05ea50a 100644 --- a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts +++ b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts @@ -237,11 +237,13 @@ function handleSpanStart(fn: () => unknown, breadcrumbOpts?: { query: string }) } function createBreadcrumb(query: string): void { + // The breadcrumb carries the same query text as the span, so it is sanitized the same way. + const queryText = sanitizeSqlQuery(query); addBreadcrumb({ category: 'query', - message: query, + message: queryText, data: { - 'db.query.text': query, + 'db.query.text': queryText, }, }); } @@ -250,18 +252,19 @@ function createBreadcrumb(query: string): void { * Creates a start span options object. */ function createStartSpanOptions(query: string, data: DatabaseSpanData): StartSpanOptions { - const querySummary = query ? getSqlQuerySummary(sanitizeSqlQuery(query)) : undefined; + const queryText = query ? sanitizeSqlQuery(query) : undefined; + const querySummary = queryText ? getSqlQuerySummary(queryText) : undefined; const client = getClient(); const name = client && hasSpanStreamingEnabled(client) ? querySummary || (data[DB_NAMESPACE] as string | undefined) || DB_SPAN_NAME_FALLBACK - : query; + : (queryText ?? DB_SPAN_NAME_FALLBACK); return { name, attributes: { - [DB_QUERY_TEXT]: query, + [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: SENTRY_ORIGIN, [SENTRY_OP]: DB_QUERY, diff --git a/packages/server-utils/src/integrations/knex.ts b/packages/server-utils/src/integrations/knex.ts index 80181a9d5d08..edc023f3118e 100644 --- a/packages/server-utils/src/integrations/knex.ts +++ b/packages/server-utils/src/integrations/knex.ts @@ -174,9 +174,12 @@ function subscribeQuery(): void { connection?.filename || connection?.database || extractDatabaseFromConnectionString(connectionString); const dbSystem = mapSystem(client?.driverName); - const dbStatement = query?.sql != null ? truncate(query.sql, MAX_QUERY_LENGTH) : undefined; const dialect = client?.driverName === 'mysql' || client?.driverName === 'mysql2' ? 'mysql' : undefined; - const querySummary = dbStatement ? getSqlQuerySummary(sanitizeSqlQuery(dbStatement, dialect)) : undefined; + // Per OTel, `db.query.text` must not carry inline literal values (e.g. from `knex.raw`), so the + // sanitized form is attached rather than the raw statement. + const dbStatement = + query?.sql != null ? sanitizeSqlQuery(truncate(query.sql, MAX_QUERY_LENGTH), dialect) : undefined; + const querySummary = dbStatement ? getSqlQuerySummary(dbStatement) : undefined; const attributes: SpanAttributes = { [SENTRY_OP]: DB, [SENTRY_KIND]: 'client', diff --git a/packages/server-utils/src/integrations/mysql.ts b/packages/server-utils/src/integrations/mysql.ts index 30ccfdcb8e60..acf8c3887ea0 100644 --- a/packages/server-utils/src/integrations/mysql.ts +++ b/packages/server-utils/src/integrations/mysql.ts @@ -88,13 +88,16 @@ function instrumentMysql(): void { // handler with the caller's context lost. `deferSpanEnd` replays this scope onto the emitter. data._sentryCallerScope = getCurrentScope(); - const querySummary = sql ? getSqlQuerySummary(sanitizeSqlQuery(sql, 'mysql')) : undefined; + // Per OTel, `db.query.text` must not carry inline literal values, so the sanitized form is + // attached rather than the raw statement. + const queryText = sql ? sanitizeSqlQuery(sql, 'mysql') : undefined; + const querySummary = queryText ? getSqlQuerySummary(queryText) : undefined; const client = getClient(); const name = client && hasSpanStreamingEnabled(client) ? querySummary || database || DB_SYSTEM_NAME_VALUE_MYSQL - : (sql ?? 'mysql.query'); + : (queryText ?? 'mysql.query'); return startInactiveSpan({ name, @@ -106,7 +109,7 @@ function instrumentMysql(): void { [ATTR_DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database), ...(database ? { [DB_NAMESPACE]: database } : {}), ...(user ? { [DB_USER]: user } : {}), - ...(sql ? { [DB_QUERY_TEXT]: sql } : {}), + ...(queryText ? { [DB_QUERY_TEXT]: queryText } : {}), [DB_QUERY_SUMMARY]: querySummary, [SERVER_ADDRESS]: host, [SERVER_PORT]: portIsNumber ? portNumber : undefined, diff --git a/packages/server-utils/src/integrations/mysql2/index.ts b/packages/server-utils/src/integrations/mysql2/index.ts index 684c0886b45e..2d9342878049 100644 --- a/packages/server-utils/src/integrations/mysql2/index.ts +++ b/packages/server-utils/src/integrations/mysql2/index.ts @@ -84,13 +84,14 @@ function subscribeQueryChannel(channelName: ChannelName): void { data => { const statement = getQueryText(data.arguments); const connectionAttributes = getConnectionAttributes(data.self?.config); - const querySummary = statement ? getSqlQuerySummary(sanitizeSqlQuery(statement, 'mysql')) : undefined; + const queryText = statement ? sanitizeSqlQuery(statement, 'mysql') : undefined; + const querySummary = queryText ? getSqlQuerySummary(queryText) : undefined; const client = getClient(); const name = client && hasSpanStreamingEnabled(client) ? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL - : (statement ?? 'mysql2.query'); + : (queryText ?? 'mysql2.query'); return startInactiveSpan({ name, @@ -99,7 +100,7 @@ function subscribeQueryChannel(channelName: ChannelName): void { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, [SENTRY_OP]: DB, [DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL, - [DB_QUERY_TEXT]: statement || undefined, + [DB_QUERY_TEXT]: queryText || undefined, [DB_QUERY_SUMMARY]: querySummary, ...connectionAttributes, }, diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/postgres.ts index d2b54e0ef089..8997b1de5e0e 100644 --- a/packages/server-utils/src/integrations/postgres.ts +++ b/packages/server-utils/src/integrations/postgres.ts @@ -180,14 +180,14 @@ function querySpanOptions(ctx: PgChannelContext): { name: string; attributes: Sp const params = (ctx.self as { connectionParameters?: PgConnectionParams } | undefined)?.connectionParameters ?? {}; const queryConfig = extractQueryConfig(ctx.arguments); const client = getClient(); - // The statement is sanitized before it is summarized, so that a string literal containing - // `from`/`join` can't leak a value into the summary. - const querySummary = queryConfig?.text ? getSqlQuerySummary(sanitizeSqlQuery(queryConfig.text)) : undefined; - + // Per OTel, `db.query.text` must not carry inline literal values. `$n` placeholders survive the + // sanitizer, so parameterized statements pass through unchanged. + const queryText = queryConfig?.text ? sanitizeSqlQuery(queryConfig.text) : undefined; + const querySummary = queryText ? getSqlQuerySummary(queryText) : undefined; const name = client && hasSpanStreamingEnabled(client) ? querySummary || params.database || DB_SYSTEM_POSTGRESQL - : (queryConfig?.text ?? SPAN_QUERY_FALLBACK); + : (queryText ?? SPAN_QUERY_FALLBACK); return { name, @@ -195,7 +195,7 @@ function querySpanOptions(ctx: PgChannelContext): { name: string; attributes: Sp [SENTRY_OP]: DB, ...getConnectionAttributes(params), [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, - [DB_QUERY_TEXT]: queryConfig?.text || undefined, + [DB_QUERY_TEXT]: queryText || undefined, [DB_QUERY_SUMMARY]: querySummary, [ATTR_PG_PLAN]: typeof queryConfig?.name === 'string' ? queryConfig.name : undefined, }, diff --git a/packages/server-utils/src/integrations/tedious.ts b/packages/server-utils/src/integrations/tedious.ts index a04935cad55b..6ed5ab70af1d 100644 --- a/packages/server-utils/src/integrations/tedious.ts +++ b/packages/server-utils/src/integrations/tedious.ts @@ -132,7 +132,8 @@ function subscribeQuery(channelName: string, operation: string): void { const databaseName = connection[currentDatabaseSymbol]; const sql = extractSql(request); - const querySummary = sql && operation !== 'callProcedure' ? getSqlQuerySummary(sanitizeSqlQuery(sql)) : undefined; + const queryText = sql ? sanitizeSqlQuery(sql) : undefined; + const querySummary = queryText && operation !== 'callProcedure' ? getSqlQuerySummary(queryText) : undefined; const attributes: SpanAttributes = { [SENTRY_OP]: DB, @@ -142,7 +143,7 @@ function subscribeQuery(channelName: string, operation: string): void { [DB_NAMESPACE]: databaseName, // `>=4` uses the `authentication` object; older versions expose `userName` directly. [DB_USER]: connection.config?.userName ?? connection.config?.authentication?.options?.userName, - [DB_QUERY_TEXT]: sql, + [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, [ATTR_DB_SQL_TABLE]: request.table, [SERVER_ADDRESS]: connection.config?.server, @@ -155,7 +156,7 @@ function subscribeQuery(channelName: string, operation: string): void { name: client && hasSpanStreamingEnabled(client) ? querySummary || getLowCardinalitySecondarySpanName(operation, databaseName, sql, request.table) - : sql || getSecondarySpanName(operation, databaseName, request.table), + : queryText || getSecondarySpanName(operation, databaseName, request.table), attributes, });