Skip to content

Commit 37d116a

Browse files
authored
feat(core): Add and use dataCollection.databaseQueryData (for e.g. Supabase filter values and mutation bodies) (#22219)
Adds `dataBaseQueryData` and uses the option in Supabase. This was previously gated by `dataCollection.userInfo` which is, however, for a different data category. See previous PR: #21085
1 parent b8e941f commit 37d116a

12 files changed

Lines changed: 59 additions & 18 deletions

File tree

packages/astro/test/server/middleware.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ describe('sentryMiddleware', () => {
6666
queryParams: true,
6767
graphQL: { document: true, variables: true },
6868
genAI: { inputs: false, outputs: false },
69+
databaseQueryData: true,
6970
stackFrameVariables: true,
7071
frameContextLines: 5,
7172
}),

packages/core/src/integrations/supabase.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,8 @@ function instrumentPostgRESTFilterBuilder(
385385
}
386386

387387
const client = getClient();
388-
const shouldSendData = _options.sendOperationData ?? client?.getDataCollectionOptions().userInfo === true;
388+
const shouldSendData =
389+
_options.sendOperationData ?? client?.getDataCollectionOptions().databaseQueryData === true;
389390
const bodyPayload = getMutationBodyPayloadForTelemetry(typedThis.body, body);
390391

391392
// Adding operation to the beginning of the description if it's not a `select` operation
@@ -563,7 +564,7 @@ interface SupabaseIntegrationOptions {
563564
* Whether to attach PostgREST query filters and mutation body payloads
564565
* to Sentry telemetry.
565566
*
566-
* Falls back to `dataCollection.userInfo` when not set.
567+
* Falls back to `dataCollection.databaseQueryData` when not set.
567568
* @default undefined
568569
*/
569570
sendOperationData?: boolean;

packages/core/src/types/datacollection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ export interface DataCollection {
7878
outputs?: boolean;
7979
};
8080

81+
/**
82+
* 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.
83+
*
84+
* 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.
85+
* @default true
86+
*/
87+
databaseQueryData?: boolean;
88+
8189
/**
8290
* Capture local variable values in stack frames.
8391
* @default true

packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function defaultPiiToCollectionOptions(sendDefaultPii?: boolean): Resolve
1818
queryParams: true,
1919
graphQL: { document: true, variables: true },
2020
genAI: { inputs: true, outputs: true },
21+
databaseQueryData: true,
2122
stackFrameVariables: true,
2223
frameContextLines: 7, // default should be 5, but ContextLines integration uses 7
2324
}
@@ -31,6 +32,9 @@ export function defaultPiiToCollectionOptions(sendDefaultPii?: boolean): Resolve
3132
// always attached regardless of `sendDefaultPii`; keep it on to preserve that behavior.
3233
graphQL: { document: true, variables: true },
3334
genAI: { inputs: false, outputs: false },
35+
// Database query values were only sent with `sendDefaultPii: true` (e.g. Supabase gated on it),
36+
// so map the legacy "off" state to `false`.
37+
databaseQueryData: false,
3438
stackFrameVariables: true,
3539
frameContextLines: 7, // default should be 5, but ContextLines integration uses 7
3640
};

packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const DEFAULTS: ResolvedDataCollection = {
99
queryParams: true,
1010
graphQL: { document: true, variables: true },
1111
genAI: { inputs: true, outputs: true },
12+
databaseQueryData: true,
1213
stackFrameVariables: true,
1314
frameContextLines: 5,
1415
};
@@ -52,6 +53,7 @@ export function resolveDataCollectionOptions(options: {
5253
inputs: dc.genAI?.inputs ?? base.genAI.inputs,
5354
outputs: dc.genAI?.outputs ?? base.genAI.outputs,
5455
},
56+
databaseQueryData: dc.databaseQueryData ?? base.databaseQueryData,
5557
stackFrameVariables: dc.stackFrameVariables ?? base.stackFrameVariables,
5658
frameContextLines: dc.frameContextLines ?? base.frameContextLines,
5759
};

packages/core/test/lib/integrations/mcp-server/testUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function createMockClient(userInfo = true, genAI?: { inputs: boolean; out
1919
queryParams: true,
2020
graphQL: { document: true, variables: true },
2121
genAI: genAIOptions,
22+
databaseQueryData: true,
2223
stackFrameVariables: true,
2324
frameContextLines: 5,
2425
}),

packages/core/test/lib/integrations/supabase.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ type CreateMockSupabaseClientOptions = {
3939
method?: string;
4040
url?: URL | string;
4141
body?: unknown;
42-
/** When set, configures the mocked Sentry client's `dataCollection.userInfo`. Omit to leave `getClient` to the test file `beforeEach`. */
43-
dataCollectionUserInfo?: boolean;
42+
/** When set, configures the mocked Sentry client's `dataCollection.databaseQueryData`. Omit to leave `getClient` to the test file `beforeEach`. */
43+
dataCollectionDatabaseQueryData?: boolean;
4444
};
4545

4646
const DEFAULT_MOCK_SUPABASE_REST_URL = 'https://example.supabase.co/rest/v1/todos';
@@ -53,9 +53,9 @@ const MOCK_SUPABASE_PII_SCENARIO: Pick<CreateMockSupabaseClientOptions, 'method'
5353
};
5454

5555
function createMockSupabaseClient(resolveWith: unknown, options?: CreateMockSupabaseClientOptions): unknown {
56-
if (options?.dataCollectionUserInfo !== undefined) {
56+
if (options?.dataCollectionDatabaseQueryData !== undefined) {
5757
currentScopesMocks.getClient.mockReturnValue({
58-
getDataCollectionOptions: () => ({ userInfo: options.dataCollectionUserInfo }),
58+
getDataCollectionOptions: () => ({ databaseQueryData: options.dataCollectionDatabaseQueryData }),
5959
} as any);
6060
}
6161

@@ -237,10 +237,10 @@ describe('Supabase Integration', () => {
237237
vi.restoreAllMocks();
238238
});
239239

240-
it('omits db.query, db.body, and breadcrumb query/body when dataCollection.userInfo is false', async () => {
240+
it('omits db.query, db.body, and breadcrumb query/body when dataCollection.databaseQueryData is false', async () => {
241241
const client = createMockSupabaseClient(
242242
{ status: 200 },
243-
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false },
243+
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false },
244244
);
245245
instrumentSupabaseClient(client);
246246

@@ -259,10 +259,10 @@ describe('Supabase Integration', () => {
259259
expect(breadcrumb).not.toHaveProperty('data');
260260
});
261261

262-
it('includes db.query, db.body, and breadcrumb query/body when dataCollection.userInfo is true', async () => {
262+
it('includes db.query, db.body, and breadcrumb query/body when dataCollection.databaseQueryData is true', async () => {
263263
const client = createMockSupabaseClient(
264264
{ status: 200 },
265-
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: true },
265+
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: true },
266266
);
267267
instrumentSupabaseClient(client);
268268

@@ -290,10 +290,10 @@ describe('Supabase Integration', () => {
290290
);
291291
});
292292

293-
it('includes data when sendOperationData option is set, regardless of dataCollection.userInfo', async () => {
293+
it('includes data when sendOperationData option is set, regardless of dataCollection.databaseQueryData', async () => {
294294
const client = createMockSupabaseClient(
295295
{ status: 200 },
296-
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false },
296+
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false },
297297
);
298298
instrumentSupabaseClient(client, { sendOperationData: true });
299299

@@ -312,10 +312,10 @@ describe('Supabase Integration', () => {
312312
);
313313
});
314314

315-
it('sendOperationData: false takes precedence over dataCollection.userInfo: true', async () => {
315+
it('sendOperationData: false takes precedence over dataCollection.databaseQueryData: true', async () => {
316316
const client = createMockSupabaseClient(
317317
{ status: 200 },
318-
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: true },
318+
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: true },
319319
);
320320
instrumentSupabaseClient(client, { sendOperationData: false });
321321

@@ -330,7 +330,7 @@ describe('Supabase Integration', () => {
330330
expect(spanOptions.attributes['db.body']).toBeUndefined();
331331
});
332332

333-
it('includes data when legacy sendDefaultPii: true is bridged to dataCollection.userInfo', async () => {
333+
it('includes data when legacy sendDefaultPii: true is bridged to dataCollection.databaseQueryData', async () => {
334334
const resolved = resolveDataCollectionOptions({ sendDefaultPii: true });
335335
currentScopesMocks.getClient.mockReturnValue({
336336
getDataCollectionOptions: () => resolved,
@@ -378,7 +378,7 @@ describe('Supabase Integration', () => {
378378
it('omits supabase error context query/body when data collection is off', async () => {
379379
const client = createMockSupabaseClient(
380380
{ status: 400, error: { message: 'Bad request', code: '400' } },
381-
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false },
381+
{ ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false },
382382
);
383383
instrumentSupabaseClient(client);
384384

@@ -417,7 +417,7 @@ describe('Supabase Integration', () => {
417417
method: 'POST',
418418
url: 'https://example.supabase.co/rest/v1/todos?columns=',
419419
body: [{ title: 'Test Todo' }],
420-
dataCollectionUserInfo: true,
420+
dataCollectionDatabaseQueryData: true,
421421
},
422422
);
423423
instrumentSupabaseClient(client);

packages/core/test/lib/utils/data-collection/defaultPiiToCollectionOptions.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('defaultPiiToCollectionOptions', () => {
1111
queryParams: true,
1212
graphQL: { document: true, variables: true },
1313
genAI: { inputs: true, outputs: true },
14+
databaseQueryData: true,
1415
stackFrameVariables: true,
1516
frameContextLines: 7,
1617
});
@@ -28,6 +29,7 @@ describe('defaultPiiToCollectionOptions', () => {
2829
queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] },
2930
graphQL: { document: true, variables: true },
3031
genAI: { inputs: false, outputs: false },
32+
databaseQueryData: false,
3133
stackFrameVariables: true,
3234
frameContextLines: 7,
3335
});
@@ -45,6 +47,7 @@ describe('defaultPiiToCollectionOptions', () => {
4547
queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] },
4648
graphQL: { document: true, variables: true },
4749
genAI: { inputs: false, outputs: false },
50+
databaseQueryData: false,
4851
stackFrameVariables: true,
4952
frameContextLines: 7,
5053
});
@@ -62,6 +65,7 @@ describe('defaultPiiToCollectionOptions', () => {
6265
queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] },
6366
graphQL: { document: true, variables: true },
6467
genAI: { inputs: false, outputs: false },
68+
databaseQueryData: false,
6569
stackFrameVariables: true,
6670
frameContextLines: 7,
6771
});

packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('resolveDataCollectionOptions', () => {
1010
queryParams: true,
1111
graphQL: { document: true, variables: true },
1212
genAI: { inputs: true, outputs: true },
13+
databaseQueryData: true,
1314
stackFrameVariables: true,
1415
frameContextLines: 5,
1516
};
@@ -24,6 +25,7 @@ describe('resolveDataCollectionOptions', () => {
2425
expect(result.genAI).toEqual({ inputs: false, outputs: false });
2526
// GraphQL documents are redacted at collection time, so they stay on to preserve legacy behavior.
2627
expect(result.graphQL).toEqual({ document: true, variables: true });
28+
expect(result.databaseQueryData).toBe(false);
2729
expect(result.stackFrameVariables).toBe(true);
2830
expect(result.frameContextLines).toBe(7);
2931
});
@@ -50,6 +52,7 @@ describe('resolveDataCollectionOptions', () => {
5052
expect(result.queryParams).toBe(true);
5153
expect(result.graphQL).toEqual({ document: true, variables: true });
5254
expect(result.genAI).toEqual({ inputs: true, outputs: true });
55+
expect(result.databaseQueryData).toBe(true);
5356
});
5457

5558
it('bridges sendDefaultPii: false to restrictive config', () => {
@@ -59,6 +62,7 @@ describe('resolveDataCollectionOptions', () => {
5962
expect(result.httpBodies).toEqual([]);
6063
expect(result.genAI).toEqual({ inputs: false, outputs: false });
6164
expect(result.graphQL).toEqual({ document: true, variables: true });
65+
expect(result.databaseQueryData).toBe(false);
6266
});
6367
});
6468

@@ -74,6 +78,7 @@ describe('resolveDataCollectionOptions', () => {
7478
// Remaining fields use spec defaults (not sendDefaultPii bridge)
7579
expect(result.httpBodies).toEqual(['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse']);
7680
expect(result.genAI).toEqual({ inputs: true, outputs: true });
81+
expect(result.databaseQueryData).toBe(true);
7782
});
7883
});
7984

@@ -94,6 +99,7 @@ describe('resolveDataCollectionOptions', () => {
9499
expect(result.queryParams).toBe(true);
95100
expect(result.graphQL).toEqual({ document: true, variables: true });
96101
expect(result.genAI).toEqual({ inputs: true, outputs: true });
102+
expect(result.databaseQueryData).toBe(true);
97103
expect(result.stackFrameVariables).toBe(true);
98104
expect(result.frameContextLines).toBe(5);
99105
});
@@ -150,13 +156,23 @@ describe('resolveDataCollectionOptions', () => {
150156

151157
expect(result.queryParams).toBe(false);
152158
});
159+
160+
it('supports turning off database query data', () => {
161+
const result = resolveDataCollectionOptions({
162+
dataCollection: {
163+
databaseQueryData: false,
164+
},
165+
});
166+
167+
expect(result.databaseQueryData).toBe(false);
168+
});
153169
});
154170

155171
describe('return type completeness', () => {
156172
it('always returns all fields', () => {
157173
const result = resolveDataCollectionOptions({});
158174

159-
expect(Object.keys(result)).toHaveLength(9);
175+
expect(Object.keys(result)).toHaveLength(10);
160176
expect(result).toHaveProperty('userInfo');
161177
expect(result).toHaveProperty('cookies');
162178
expect(result).toHaveProperty('httpHeaders');
@@ -170,6 +186,7 @@ describe('resolveDataCollectionOptions', () => {
170186
expect(result).toHaveProperty('genAI');
171187
expect(result).toHaveProperty('genAI.inputs');
172188
expect(result).toHaveProperty('genAI.outputs');
189+
expect(result).toHaveProperty('databaseQueryData');
173190
expect(result).toHaveProperty('stackFrameVariables');
174191
expect(result).toHaveProperty('frameContextLines');
175192
});

packages/core/test/lib/utils/request.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,7 @@ describe('request utils', () => {
876876
queryParams: true,
877877
graphQL: { document: true, variables: true },
878878
genAI: { inputs: true, outputs: true },
879+
databaseQueryData: true,
879880
stackFrameVariables: true,
880881
frameContextLines: 5,
881882
};

0 commit comments

Comments
 (0)