Skip to content

Commit 1591cf7

Browse files
authored
Merge branch 'develop' into fix/deno-tracing-gate
2 parents f003516 + 824ebf3 commit 1591cf7

89 files changed

Lines changed: 1227 additions & 1010 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.cursor/BUGBOT.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ Keep reviews high-signal. Prefer actionable, high-confidence findings over specu
6868
- `consoleSandbox(() => { console.warn(...) })` for intentional user-facing warnings (e.g. init-time misconfiguration messages). The `consoleSandbox` wrapper prevents the SDK's own console instrumentation from intercepting the call. Bare `console.*` calls outside very early init paths (e.g. before the logger is available) should be flagged.
6969
- Flag `url.full`, `url.query`, `http.target` or `request.query_string` being set from a URL that isn't filtered. Wrap the value in `filterCollectedUrl()` (or `filterCollectedUrlQuery()` for a bare query string), passing the `client` if one is in scope, so `dataCollection.urlQueryParams` applies. Values that can't contain a query (a bare pathname, a queue URL) are fine. The `sdk/no-unfiltered-url-attributes` lint rule catches direct attribute writes, so look for what it can't: URLs passed through a helper or variable first, deprecated aliases set next to a filtered attribute, and URLs on breadcrumbs or events instead of spans.
7070
- Flag span names built from a raw URL. Names follow `METHOD scheme://host/path` and must never contain a query string, so they need `stripUrlQueryAndFragment()`, not `filterCollectedUrl()`.
71+
- Flag a SQL statement that reaches telemetry unsanitized. `db.query.text`, `db.query.summary`, a DB span name, and a breadcrumb carrying a statement all have to come from `sanitizeSqlQuery()` or `sanitizeSqlQueryWithSummary()` (`@sentry/server-utils`). Inline literals are user data, and OTel allows collecting query text only once they are replaced with `?`. This is deliberately not gated on `dataCollection.databaseQueryData`, which does not cover query text.
72+
- Cover every place the statement lands, not only the span attribute. The two that get forgotten are the breadcrumb beside the span and the span name used when span streaming is off.
73+
- Sanitize each statement of a batch before joining them.
74+
- Pass the dialect. `toSqlDialect()` maps a driver or `db.system.name` value to one, and a missing dialect leaves MySQL and SQL Server values in the statement.
75+
- Leave Redis command text alone. It is not SQL and has its own redaction path.
7176
- Flag usage of the following APIs: `getCurrentScope()`, `getIsolationScope()`, `getClient()` if they are avoidable. Flag it with severity Low and acknowledge from the start that this is more a "is this necessary" check, rather than a rule violation.
7277
- Reason for flagging: Usage of these APIs is problematic for multi-client setups where either there is no "current" client/scope, or the wrong client might be used. Calling these APIs would create a current scope, thereby misleading any future calls to these APIs.
7378
- What to do instead: Use an existing reference to the scope or client. For example, this is possible in most `Integration` hooks.

‎dev-packages/cloudflare-integration-tests/suites/prisma/test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ it('captures a transaction with Prisma spans for a D1 query via the @sentry/clou
5555
origin: 'auto.db.cloudflare.d1',
5656
},
5757
{
58-
description: expect.stringMatching(
59-
/^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' \*\/$/,
60-
),
58+
// The sanitizer strips the D1 adapter's traceparent comment and replaces the literals.
59+
description:
60+
'SELECT `main`.`User`.`id`, `main`.`User`.`email`, `main`.`User`.`name` FROM `main`.`User` WHERE ?=? LIMIT ? OFFSET ?',
6161
op: 'db.query',
6262
origin: 'auto.db.cloudflare.d1',
6363
},

‎dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ Deno.test('mysql instrumentation: orchestrion:mysql:query channel produces a nes
9393

9494
const mysqlSpan = parent.spans?.find(s => s.op === 'db');
9595
assertExists(mysqlSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
96-
assertEquals(mysqlSpan!.description, 'SELECT 1 AS solution');
96+
assertEquals(mysqlSpan!.description, 'SELECT ? AS solution');
9797
assertEquals(mysqlSpan!.data?.['db.system.name'], 'mysql');
98-
assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT 1 AS solution');
98+
assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT ? AS solution');
9999
assertEquals(mysqlSpan!.data?.['server.address'], '127.0.0.1');
100100
assertEquals(mysqlSpan!.data?.['server.port'], 3306);
101101
assertEquals(mysqlSpan!.data?.['db.user'], 'root');

‎dev-packages/deno-integration-tests/suites/orchestrion-mysql2/test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ Deno.test('mysql2 instrumentation: orchestrion:mysql2:query channel produces a n
5454

5555
const mysqlSpan = parent.spans?.find(s => s.op === 'db');
5656
assertExists(mysqlSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
57-
assertEquals(mysqlSpan!.description, 'SELECT 1 AS solution');
57+
assertEquals(mysqlSpan!.description, 'SELECT ? AS solution');
5858
assertEquals(mysqlSpan!.data?.['db.system.name'], 'mysql');
59-
assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT 1 AS solution');
59+
assertEquals(mysqlSpan!.data?.['db.query.text'], 'SELECT ? AS solution');
6060
assertEquals(mysqlSpan!.data?.['db.namespace'], 'mydb');
6161
assertEquals(mysqlSpan!.data?.['db.user'], 'root');
6262
assertEquals(mysqlSpan!.data?.['server.address'], '127.0.0.1');

‎dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ Deno.test('pg instrumentation: orchestrion:pg:query channel produces a nested db
9393

9494
const pgSpan = parent.spans?.find(s => s.op === 'db');
9595
assertExists(pgSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
96-
assertEquals(pgSpan!.description, 'SELECT 1 AS solution');
96+
assertEquals(pgSpan!.description, 'SELECT ? AS solution');
9797
assertEquals(pgSpan!.data?.['db.system.name'], 'postgresql');
98-
assertEquals(pgSpan!.data?.['db.query.text'], 'SELECT 1 AS solution');
98+
assertEquals(pgSpan!.data?.['db.query.text'], 'SELECT ? AS solution');
9999
assertEquals(pgSpan!.data?.['server.address'], '127.0.0.1');
100100
assertEquals(pgSpan!.data?.['server.port'], 5432);
101101
assertEquals(pgSpan!.data?.['db.user'], 'root');

‎dev-packages/deno-integration-tests/suites/orchestrion-tedious/test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ Deno.test('tedious instrumentation: orchestrion:tedious:execSql channel produces
5757

5858
const tediousSpan = parent.spans?.find(s => s.op === 'db');
5959
assertExists(tediousSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
60-
assertEquals(tediousSpan!.description, 'SELECT 1');
60+
assertEquals(tediousSpan!.description, 'SELECT ?');
6161
assertEquals(tediousSpan!.data?.['db.system.name'], 'mssql');
6262
assertEquals(tediousSpan!.data?.['db.namespace'], 'mydb');
6363
assertEquals(tediousSpan!.data?.['db.user'], 'sa');
64-
assertEquals(tediousSpan!.data?.['db.query.text'], 'SELECT 1');
64+
assertEquals(tediousSpan!.data?.['db.query.text'], 'SELECT ?');
6565
assertEquals(tediousSpan!.data?.['server.address'], '127.0.0.1');
6666
assertEquals(tediousSpan!.data?.['server.port'], 1433);
6767
assertEquals(tediousSpan!.data?.['sentry.origin'], 'auto.db.tedious');

‎dev-packages/e2e-tests/test-applications/astro-6-cf-workers/tests/db.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ test('a real mysql query emits a db span with orchestrion-channel attributes', a
1212
const spans = await spansPromise;
1313
const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
1414

15-
const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution');
15+
const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution');
1616
expect(firstQuery).toBeDefined();
1717
// With span streaming the span name is the low-cardinality query summary; the statement stays in
1818
// `db.query.text`.
1919
expect(firstQuery!.name).toBe('SELECT');
2020
expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql');
2121
expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql');
22-
expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT 1 + 1 AS solution');
22+
expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT ? + ? AS solution');
2323
expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1');
2424
expect(firstQuery!.attributes['server.port']?.value).toBe(3306);
2525
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 ({
3535
const queryTexts = spans
3636
.filter(span => getSpanOp(span) === 'db')
3737
.map(span => span.attributes['db.query.text']?.value);
38-
expect(queryTexts).toContain('SELECT 1 + 1 AS solution');
38+
expect(queryTexts).toContain('SELECT ? + ? AS solution');
3939
expect(queryTexts).toContain('SELECT NOW()');
4040
});

‎dev-packages/e2e-tests/test-applications/astro-7-static/tests/db.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ test('Instruments mysql automatically', async ({ baseURL }) => {
5858
expect.objectContaining({
5959
op: 'db',
6060
origin: 'auto.db.mysql',
61-
description: 'SELECT 1 + 1 AS solution',
61+
description: 'SELECT ? + ? AS solution',
6262
status: 'ok',
6363
data: expect.objectContaining({
6464
'db.system.name': 'mysql',
65-
'db.query.text': 'SELECT 1 + 1 AS solution',
65+
'db.query.text': 'SELECT ? + ? AS solution',
6666
'db.user': 'root',
6767
'db.connection_string': expect.any(String),
6868
'server.address': expect.any(String),

‎dev-packages/e2e-tests/test-applications/astro-7/tests/db.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ test('Instruments mysql automatically', async ({ baseURL }) => {
6262
}),
6363
});
6464

65-
expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT 1 + 1 AS solution'));
65+
expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT ? + ? AS solution'));
6666
expect(mysqlSpans).toContainEqual(expectedQuerySpan('SELECT NOW()'));
6767
});

‎dev-packages/e2e-tests/test-applications/bun-mysql/tests/mysql.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ test('mysql queries emit a db span with orchestrion-channel attributes', async (
2525
const spans = await spansPromise;
2626
const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
2727

28-
const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution');
28+
const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT ? + ? AS solution');
2929
expect(firstQuery).toBeDefined();
3030
// With span streaming, db span names are the low-cardinality query summary, not the raw SQL
3131
expect(firstQuery!.name).toBe('SELECT');
3232
expect(firstQuery!.attributes).toMatchObject({
3333
'sentry.origin': { value: 'auto.db.mysql', type: 'string' },
3434
'db.system.name': { value: 'mysql', type: 'string' },
35-
'db.query.text': { value: 'SELECT 1 + 1 AS solution', type: 'string' },
35+
'db.query.text': { value: 'SELECT ? + ? AS solution', type: 'string' },
3636
'server.port': { value: 3306, type: 'integer' },
3737
'db.user': { value: 'root', type: 'string' },
3838
});
@@ -58,7 +58,7 @@ test('a nested query lands on the same trace (async context restored)', async ({
5858
const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
5959

6060
const queries = dbSpans.map(span => span.attributes['db.query.text']?.value);
61-
expect(queries).toContain('SELECT 1 + 1 AS solution');
61+
expect(queries).toContain('SELECT ? + ? AS solution');
6262
expect(queries).toContain('SELECT NOW()');
6363
expect(dbSpans.every(span => span.parent_span_id === segment.span_id)).toBe(true);
6464
});

0 commit comments

Comments
 (0)