Skip to content

Commit f7b506d

Browse files
authored
feat(metrics): Migrate metrics to use dataCollection instead of sendDefaultPii (#21078)
Migrates metrics code from reading sendDefaultPii off client options to using the new `client.getDataCollectionOptions().userInfo` API. This is a 1:1 behavioral mapping — userInfo controls the same auto-population of user identity fields (IP, User-Agent) that `sendDefaultPii` did. closes #21051
1 parent f55fc30 commit f7b506d

4 files changed

Lines changed: 122 additions & 3 deletions

File tree

packages/browser-utils/src/metrics/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ export function startStandaloneWebVitalSpan(options: StandaloneWebVitalSpanOptio
8585

8686
const { name, transaction, attributes: passedAttributes, startTime } = options;
8787

88-
const { release, environment, sendDefaultPii } = client.getOptions();
88+
const { release, environment } = client.getOptions();
89+
const { userInfo } = client.getDataCollectionOptions();
8990
// We need to get the replay, user, and activeTransaction from the current scope
9091
// so that we can associate replay id, profile id, and a user display to the span
9192
const replay = client.getIntegrationByName<Integration & { getReplayId: () => string }>('Replay');
@@ -120,7 +121,7 @@ export function startStandaloneWebVitalSpan(options: StandaloneWebVitalSpanOptio
120121
'user_agent.original': WINDOW.navigator?.userAgent,
121122

122123
// This tells Sentry to infer the IP address from the request
123-
'client.address': sendDefaultPii ? '{{auto}}' : undefined,
124+
'client.address': userInfo ? '{{auto}}' : undefined,
124125

125126
...passedAttributes,
126127
};
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as SentryCore from '@sentry/core';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { startStandaloneWebVitalSpan } from '../../src/metrics/utils';
4+
5+
vi.mock('@sentry/core', async () => {
6+
const actual = await vi.importActual('@sentry/core');
7+
return {
8+
...actual,
9+
getClient: vi.fn(),
10+
getCurrentScope: vi.fn(),
11+
startInactiveSpan: vi.fn(),
12+
};
13+
});
14+
15+
vi.mock('../../src/types', () => ({
16+
WINDOW: {
17+
navigator: { userAgent: 'test-user-agent' },
18+
},
19+
}));
20+
21+
describe('startStandaloneWebVitalSpan', () => {
22+
const mockScope = {
23+
getUser: vi.fn().mockReturnValue(undefined),
24+
getScopeData: vi.fn().mockReturnValue({ contexts: {} }),
25+
};
26+
27+
beforeEach(() => {
28+
vi.mocked(SentryCore.getCurrentScope).mockReturnValue(mockScope as any);
29+
vi.mocked(SentryCore.startInactiveSpan).mockReturnValue({ end: vi.fn() } as any);
30+
});
31+
32+
afterEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
it('sets client.address to {{auto}} when dataCollection.userInfo is true', () => {
37+
const mockClient = {
38+
getOptions: vi.fn().mockReturnValue({ release: '1.0', environment: 'test' }),
39+
getDataCollectionOptions: vi.fn().mockReturnValue({ userInfo: true }),
40+
getIntegrationByName: vi.fn().mockReturnValue(undefined),
41+
};
42+
vi.mocked(SentryCore.getClient).mockReturnValue(mockClient as any);
43+
44+
startStandaloneWebVitalSpan({
45+
name: 'test-vital',
46+
attributes: {},
47+
startTime: 1.0,
48+
});
49+
50+
expect(SentryCore.startInactiveSpan).toHaveBeenCalledWith(
51+
expect.objectContaining({
52+
attributes: expect.objectContaining({
53+
'client.address': '{{auto}}',
54+
}),
55+
}),
56+
);
57+
});
58+
59+
it('does not set client.address when dataCollection.userInfo is false', () => {
60+
const mockClient = {
61+
getOptions: vi.fn().mockReturnValue({ release: '1.0', environment: 'test' }),
62+
getDataCollectionOptions: vi.fn().mockReturnValue({ userInfo: false }),
63+
getIntegrationByName: vi.fn().mockReturnValue(undefined),
64+
};
65+
vi.mocked(SentryCore.getClient).mockReturnValue(mockClient as any);
66+
67+
startStandaloneWebVitalSpan({
68+
name: 'test-vital',
69+
attributes: {},
70+
startTime: 1.0,
71+
});
72+
73+
expect(SentryCore.startInactiveSpan).toHaveBeenCalledWith(
74+
expect.objectContaining({
75+
attributes: expect.not.objectContaining({
76+
'client.address': expect.anything(),
77+
}),
78+
}),
79+
);
80+
});
81+
});

packages/core/src/metrics/internal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export function _INTERNAL_flushMetricsBuffer(client: Client, maybeMetricBuffer?:
230230
clientOptions._metadata,
231231
clientOptions.tunnel,
232232
client.getDsn(),
233-
clientOptions.sendDefaultPii,
233+
client.getDataCollectionOptions().userInfo,
234234
);
235235

236236
// Clear the metric buffer after envelopes have been constructed.

packages/core/test/lib/metrics/internal.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '../../../src/metrics/internal';
88
import type { Metric } from '../../../src/types/metric';
99
import * as loggerModule from '../../../src/utils/debug-logger';
10+
import * as isBrowserModule from '../../../src/utils/isBrowser';
1011
import * as timeModule from '../../../src/utils/time';
1112
import { _INTERNAL_resetSequenceNumber } from '../../../src/utils/timestampSequence';
1213
import { getDefaultTestClientOptions, TestClient } from '../../mocks/client';
@@ -252,6 +253,42 @@ describe('_INTERNAL_captureMetric', () => {
252253
expect(buffer?.[0]?.name).toBe('trigger.flush');
253254
});
254255

256+
it('includes ingest_settings with auto when dataCollection.userInfo is true', () => {
257+
vi.spyOn(isBrowserModule, 'isBrowser').mockReturnValue(true);
258+
259+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, dataCollection: { userInfo: true } });
260+
const client = new TestClient(options);
261+
const scope = new Scope();
262+
scope.setClient(client);
263+
264+
_INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope });
265+
266+
const sendEnvelope = vi.spyOn(client as any, 'sendEnvelope').mockImplementation(() => {});
267+
_INTERNAL_flushMetricsBuffer(client);
268+
269+
const envelope = sendEnvelope.mock.calls[0]![0];
270+
const envelopeItemPayload = envelope[1][0][1];
271+
expect(envelopeItemPayload.ingest_settings).toEqual({ infer_ip: 'auto', infer_user_agent: 'auto' });
272+
});
273+
274+
it('includes ingest_settings with never when dataCollection.userInfo is not set', () => {
275+
vi.spyOn(isBrowserModule, 'isBrowser').mockReturnValue(true);
276+
277+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
278+
const client = new TestClient(options);
279+
const scope = new Scope();
280+
scope.setClient(client);
281+
282+
_INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope });
283+
284+
const sendEnvelope = vi.spyOn(client as any, 'sendEnvelope').mockImplementation(() => {});
285+
_INTERNAL_flushMetricsBuffer(client);
286+
287+
const envelope = sendEnvelope.mock.calls[0]![0];
288+
const envelopeItemPayload = envelope[1][0][1];
289+
expect(envelopeItemPayload.ingest_settings).toEqual({ infer_ip: 'never', infer_user_agent: 'never' });
290+
});
291+
255292
it('does not flush metrics buffer when it is empty', () => {
256293
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
257294
const client = new TestClient(options);

0 commit comments

Comments
 (0)