diff --git a/CHANGELOG.md b/CHANGELOG.md index 50c7c9202486..094690568d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## Unreleased + +### Important Changes + +- **fix(browser): Ensure IP address is only inferred by Relay if `sendDefaultPii` is `true`** + +This release includes a fix for a [behaviour change](https://docs.sentry.io/platforms/javascript/migration/v8-to-v9/#behavior-changes) +that was originally introduced with v9 of the SDK: User IP Addresses should only be added to Sentry events automatically, +if `sendDefaultPii` was set to `true`. + +However, the change in v9 required further internal adjustment, which should have been included in v10 of the SDK. +Unfortunately, the change did not make it into the initial v10 version but is now applied with `10.4.0`. +There is _no API_ breakage involved and hence it is safe to update. +However, after updating the SDK, events (errors, traces, replays, etc.) sent from the browser, will only include +user IP addresses, if you set `sendDefaultPii: true` in your `Sentry.init` options. + +We apologize for any inconvenience caused! + ## 10.3.0 - feat(core): MCP Server - Capture prompt results from prompt function calls (#17284) diff --git a/MIGRATION.md b/MIGRATION.md index ceaa6578e8eb..84d4e63da562 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -80,6 +80,18 @@ The removal entails **no breaking API changes**. However, in rare cases, you mig - If you set up Sentry Alerts that depend on FID, be aware that these could trigger once you upgrade the SDK, due to a lack of new values. To replace them, adjust your alerts (or dashbaords) to use INP. +### Update: User IP Address collection gated by `sendDefaultPii` + +Version `10.4.0` introduced a change that should have ideally been introduced with `10.0.0` of the SDK. +Originally destined for [version `9.0.0`](https://docs.sentry.io/platforms/javascript/migration/v8-to-v9/#behavior-changes), but having not the desired effect until v10, +SDKs will now control IP address inference of user IP addresses depending on the value of the top level `sendDefaultPii` init option. + +- If `sendDefaultPii` is `true`, Sentry will infer the IP address of users' devices to events (errors, traces, replays, etc) in all browser-based SDKs. +- If `sendDefaultPii` is `false` or not set, Sentry will not infer or collect IP address data. + +Given that this was already the advertised behaviour since v9, we classify the change [as a fix](https://github.com/getsentry/sentry-javascript/pull/17364), +though we recognize the potential impact of it. We apologize for any inconvenience caused. + ## No Version Support Timeline Version support timelines are stressful for everybody using the SDK, so we won't be defining one. diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts index c93ce0453f83..ffc00cc8258c 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts @@ -61,6 +61,9 @@ sentryTest('should capture feedback with custom button', async ({ getLocalTestUr version: expect.any(String), name: 'sentry.javascript.browser', packages: expect.anything(), + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts index e6eb920f64a5..9d6cf1a8a1f1 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts @@ -61,6 +61,9 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => { version: expect.any(String), name: 'sentry.javascript.browser', packages: expect.anything(), + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts index 66653ce68a82..d76fd2089413 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts @@ -95,6 +95,9 @@ sentryTest('should capture feedback', async ({ forceFlushReplay, getLocalTestUrl version: expect.any(String), name: 'sentry.javascript.browser', packages: expect.anything(), + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts index 69c715654921..cb683ce4fa36 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts @@ -61,6 +61,9 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => { version: expect.any(String), name: 'sentry.javascript.browser', packages: expect.anything(), + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts index 7ba25a3899b0..4637fcc5555d 100644 --- a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts +++ b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts @@ -41,6 +41,9 @@ sentryTest('allows to setup a client manually & capture exceptions', async ({ ge name: 'sentry.javascript.browser', version: expect.any(String), packages: [{ name: expect.any(String), version: expect.any(String) }], + settings: { + infer_ip: 'never', + }, }, contexts: { trace: { trace_id: expect.stringMatching(/[a-f0-9]{32}/), span_id: expect.stringMatching(/[a-f0-9]{16}/) }, diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureException/simpleError/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureException/simpleError/test.ts index e5c38d738ec7..e8474472af35 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/captureException/simpleError/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/captureException/simpleError/test.ts @@ -39,5 +39,8 @@ sentryTest('should capture correct SDK metadata', async ({ getLocalTestUrl, page version: SDK_VERSION, }, ], + settings: { + infer_ip: 'never', + }, }); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts index 06a5ede3215a..453dbafa0c37 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts @@ -1,10 +1,12 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; -sentryTest('should default user to {{auto}} on errors when sendDefaultPii: true', async ({ getLocalTestUrl, page }) => { - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - expect(eventData.user?.ip_address).toBe('{{auto}}'); -}); +sentryTest( + 'sets sdk.settings.infer_ip to "auto" on errors when sendDefaultPii: true', + async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + const eventData = await envelopeRequestParser(await waitForErrorRequestOnUrl(page, url)); + expect(eventData.sdk?.settings?.infer_ip).toBe('auto'); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts index 6e1f20826548..e624afcd2117 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts @@ -7,7 +7,7 @@ import { } from '../../../../utils/helpers'; sentryTest( - 'should default user to {{auto}} on transactions when sendDefaultPii: true', + 'sets user.ip_address to "auto" on transactions when sendDefaultPii: true', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -16,6 +16,6 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); const req = await waitForTransactionRequestOnUrl(page, url); const transaction = envelopeRequestParser(req); - expect(transaction.user?.ip_address).toBe('{{auto}}'); + expect(transaction.sdk?.settings?.infer_ip).toBe('auto'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts index 59bc021ce0a1..a24d5e44ae04 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts @@ -3,7 +3,7 @@ import { sentryTest } from '../../../../utils/fixtures'; import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; sentryTest( - 'replay recording should contain default performance spans', + 'sets sdk.settings.infer_ip to "auto" on replay events when sendDefaultPii: true', async ({ getLocalTestUrl, page, browserName }) => { // We only test this against the NPM package and replay bundles // and only on chromium as most performance entries are only available in chromium @@ -18,6 +18,6 @@ sentryTest( await page.goto(url); const replayEvent = getReplayEvent(await reqPromise0); - expect(replayEvent.user?.ip_address).toBe('{{auto}}'); + expect(replayEvent.sdk?.settings?.infer_ip).toBe('auto'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts index 7f047337dae3..4c963a737e97 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts @@ -3,7 +3,7 @@ import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; sentryTest( - 'should default user to {{auto}} on sessions when sendDefaultPii: true', + 'sets attrs.ip_address user to {{auto}} on sessions when sendDefaultPii: true', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); const session = await getFirstSentryEnvelopeRequest(page, url); diff --git a/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts b/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts index 13e6786a6a60..8166d35df2d0 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts @@ -11,7 +11,7 @@ sentryTest('should unset user', async ({ getLocalTestUrl, page }) => { expect(eventData[0].message).toBe('no_user'); // because sendDefaultPii: true - expect(eventData[0].user).toEqual({ ip_address: '{{auto}}' }); + expect(eventData[0].sdk?.settings?.infer_ip).toBe('auto'); expect(eventData[1].message).toBe('user'); expect(eventData[1].user).toEqual({ @@ -23,7 +23,5 @@ sentryTest('should unset user', async ({ getLocalTestUrl, page }) => { expect(eventData[2].message).toBe('unset_user'); // because sendDefaultPii: true - expect(eventData[2].user).toEqual({ - ip_address: '{{auto}}', - }); + expect(eventData[2].sdk?.settings?.infer_ip).toBe('auto'); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/setUser/update_user/test.ts b/dev-packages/browser-integration-tests/suites/public-api/setUser/update_user/test.ts index bf26aae4d61a..19b6b7f75576 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/setUser/update_user/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/setUser/update_user/test.ts @@ -13,10 +13,11 @@ sentryTest('should update user', async ({ getLocalTestUrl, page }) => { id: 'foo', ip_address: 'bar', }); + expect(eventData[0].sdk?.settings?.infer_ip).toBe('auto'); expect(eventData[1].message).toBe('second_user'); expect(eventData[1].user).toEqual({ id: 'baz', - ip_address: '{{auto}}', }); + expect(eventData[1].sdk?.settings?.infer_ip).toBe('auto'); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts index 75a459ef69d6..5ec77ed3938f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts @@ -11,34 +11,28 @@ sentryTest('should allow nested scoping', async ({ getLocalTestUrl, page }) => { expect(eventData[0].message).toBe('root_before'); expect(eventData[0].user).toEqual({ id: 'qux', - ip_address: '{{auto}}', // because sendDefaultPii: true }); expect(eventData[0].tags).toBeUndefined(); expect(eventData[1].message).toBe('outer_before'); expect(eventData[1].user).toEqual({ id: 'qux', - ip_address: '{{auto}}', // because sendDefaultPii: true }); expect(eventData[1].tags).toMatchObject({ foo: false }); expect(eventData[2].message).toBe('inner'); - expect(eventData[2].user).toEqual({ - ip_address: '{{auto}}', // because sendDefaultPii: true - }); + expect(eventData[2].user).toEqual({}); expect(eventData[2].tags).toMatchObject({ foo: false, bar: 10 }); expect(eventData[3].message).toBe('outer_after'); expect(eventData[3].user).toEqual({ id: 'baz', - ip_address: '{{auto}}', // because sendDefaultPii: true }); expect(eventData[3].tags).toMatchObject({ foo: false }); expect(eventData[4].message).toBe('root_after'); expect(eventData[4].user).toEqual({ id: 'qux', - ip_address: '{{auto}}', // because sendDefaultPii: true }); expect(eventData[4].tags).toBeUndefined(); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index 85dd7c27440a..8167552fb0d6 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -47,6 +47,9 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT ]), version: SDK_VERSION, name: 'sentry.javascript.browser', + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, @@ -85,6 +88,9 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT ]), version: SDK_VERSION, name: 'sentry.javascript.browser', + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index 9b44ba6b218a..f6c3dcf17b23 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -47,6 +47,9 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g ]), version: SDK_VERSION, name: 'sentry.javascript.browser', + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, @@ -85,6 +88,9 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g ]), version: SDK_VERSION, name: 'sentry.javascript.browser', + settings: { + infer_ip: 'never', + }, }, request: { url: `${TEST_HOST}/index.html`, diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 97787c2de26e..53a9e733a908 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -30,6 +30,9 @@ const DEFAULT_REPLAY_EVENT = { ]), version: SDK_VERSION, name: 'sentry.javascript.browser', + settings: { + infer_ip: 'never', + }, }, request: { url: expect.stringContaining('/index.html'), diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index a537013f7c22..20cad73269e5 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -41,6 +41,9 @@ describe('Sentry client SDK', () => { { name: 'npm:@sentry/astro', version: SDK_VERSION }, { name: 'npm:@sentry/browser', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }), diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index b6c05afe70f7..65561c29e9de 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -12,7 +12,6 @@ import type { import { _INTERNAL_flushLogsBuffer, addAutoIpAddressToSession, - addAutoIpAddressToUser, applySdkMetadata, Client, getSDKSource, @@ -83,6 +82,15 @@ export class BrowserClient extends Client { const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource(); applySdkMetadata(opts, 'browser', ['browser'], sdkSource); + // Only allow IP inferral by Relay if sendDefaultPii is true + if (opts._metadata?.sdk) { + opts._metadata.sdk.settings = { + infer_ip: opts.sendDefaultPii ? 'auto' : 'never', + // purposefully allowing already passed settings to override the default + ...opts._metadata.sdk.settings, + }; + } + super(opts); const { sendDefaultPii, sendClientReports, enableLogs } = this._options; @@ -117,7 +125,6 @@ export class BrowserClient extends Client { } if (sendDefaultPii) { - this.on('postprocessEvent', addAutoIpAddressToUser); this.on('beforeSendSession', addAutoIpAddressToSession); } } diff --git a/packages/browser/test/client.test.ts b/packages/browser/test/client.test.ts index 2197b6ed03c0..c6cbc735c8a1 100644 --- a/packages/browser/test/client.test.ts +++ b/packages/browser/test/client.test.ts @@ -184,3 +184,93 @@ describe('applyDefaultOptions', () => { WINDOW.SENTRY_RELEASE = releaseBefore; }); }); + +describe('SDK metadata', () => { + describe('sdk.settings', () => { + it('sets infer_ipto "never" by default', () => { + const options = getDefaultBrowserClientOptions({}); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.settings?.infer_ip).toBe('never'); + }); + + it('sets infer_ip to "never" if sendDefaultPii is false', () => { + const options = getDefaultBrowserClientOptions({ + sendDefaultPii: false, + }); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.settings?.infer_ip).toBe('never'); + }); + + it('sets infer_ip to "auto" if sendDefaultPii is true', () => { + const options = getDefaultBrowserClientOptions({ + sendDefaultPii: true, + }); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.settings?.infer_ip).toBe('auto'); + }); + + it("doesn't override already set sdk metadata settings", () => { + const options = getDefaultBrowserClientOptions({ + sendDefaultPii: true, + _metadata: { + sdk: { + settings: { + infer_ip: 'never', + // @ts-expect-error -- not typed but let's test anyway + other_random_setting: 'some value', + }, + }, + }, + }); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.settings).toEqual({ + infer_ip: 'never', + other_random_setting: 'some value', + }); + }); + + it('still sets infer_ip if other SDK metadata was already passed in', () => { + const options = getDefaultBrowserClientOptions({ + _metadata: { + sdk: { + name: 'sentry.javascript.angular', + }, + }, + }); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk).toEqual({ + name: 'sentry.javascript.angular', + settings: { + infer_ip: 'never', + }, + }); + }); + }); + + describe('sdk data', () => { + it('sets sdk.name to "sentry.javascript.browser" by default', () => { + const options = getDefaultBrowserClientOptions({}); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.name).toBe('sentry.javascript.browser'); + }); + + it("doesn't override already set sdk metadata", () => { + const options = getDefaultBrowserClientOptions({ + _metadata: { + sdk: { + name: 'sentry.javascript.angular', + }, + }, + }); + const client = new BrowserClient(options); + + expect(client.getOptions()._metadata?.sdk?.name).toBe('sentry.javascript.angular'); + }); + }); +}); diff --git a/packages/core/src/envelope.ts b/packages/core/src/envelope.ts index 6ae8fa718638..875056890e0e 100644 --- a/packages/core/src/envelope.ts +++ b/packages/core/src/envelope.ts @@ -32,16 +32,31 @@ import { showSpanDropWarning, spanToJSON } from './utils/spanUtils'; /** * Apply SdkInfo (name, version, packages, integrations) to the corresponding event key. * Merge with existing data if any. + * + * @internal, exported only for testing **/ -function enhanceEventWithSdkInfo(event: Event, sdkInfo?: SdkInfo): Event { - if (!sdkInfo) { +export function _enhanceEventWithSdkInfo(event: Event, newSdkInfo?: SdkInfo): Event { + if (!newSdkInfo) { return event; } - event.sdk = event.sdk || {}; - event.sdk.name = event.sdk.name || sdkInfo.name; - event.sdk.version = event.sdk.version || sdkInfo.version; - event.sdk.integrations = [...(event.sdk.integrations || []), ...(sdkInfo.integrations || [])]; - event.sdk.packages = [...(event.sdk.packages || []), ...(sdkInfo.packages || [])]; + + const eventSdkInfo = event.sdk || {}; + + event.sdk = { + ...eventSdkInfo, + name: eventSdkInfo.name || newSdkInfo.name, + version: eventSdkInfo.version || newSdkInfo.version, + integrations: [...(event.sdk?.integrations || []), ...(newSdkInfo.integrations || [])], + packages: [...(event.sdk?.packages || []), ...(newSdkInfo.packages || [])], + settings: + event.sdk?.settings || newSdkInfo.settings + ? { + ...event.sdk?.settings, + ...newSdkInfo.settings, + } + : undefined, + }; + return event; } @@ -85,7 +100,7 @@ export function createEventEnvelope( */ const eventType = event.type && event.type !== 'replay_event' ? event.type : 'event'; - enhanceEventWithSdkInfo(event, metadata?.sdk); + _enhanceEventWithSdkInfo(event, metadata?.sdk); const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0747258113a9..f81a6937d89c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -63,7 +63,10 @@ export { hasSpansEnabled } from './utils/hasSpansEnabled'; export { isSentryRequestUrl } from './utils/isSentryRequestUrl'; export { handleCallbackErrors } from './utils/handleCallbackErrors'; export { parameterize, fmt } from './utils/parameterize'; -export { addAutoIpAddressToSession, addAutoIpAddressToUser } from './utils/ipAddress'; + +export { addAutoIpAddressToSession } from './utils/ipAddress'; +// eslint-disable-next-line deprecation/deprecation +export { addAutoIpAddressToUser } from './utils/ipAddress'; export { convertSpanLinksForEnvelope, spanToTraceHeader, diff --git a/packages/core/src/types-hoist/sdkinfo.ts b/packages/core/src/types-hoist/sdkinfo.ts index b287ef0674f5..5750bdb37760 100644 --- a/packages/core/src/types-hoist/sdkinfo.ts +++ b/packages/core/src/types-hoist/sdkinfo.ts @@ -1,8 +1,14 @@ import type { Package } from './package'; +/** + * See https://develop.sentry.dev/sdk/data-model/event-payloads/sdk/#attributes + */ export interface SdkInfo { name?: string; version?: string; integrations?: string[]; packages?: Package[]; + settings?: { + infer_ip?: 'auto' | 'never'; + }; } diff --git a/packages/core/src/utils/ipAddress.ts b/packages/core/src/utils/ipAddress.ts index c481cd866e81..8c71835c9800 100644 --- a/packages/core/src/utils/ipAddress.ts +++ b/packages/core/src/utils/ipAddress.ts @@ -7,6 +7,7 @@ import type { User } from '../types-hoist/user'; /** * @internal + * @deprecated -- set ip inferral via via SDK metadata options on client instead. */ export function addAutoIpAddressToUser(objWithMaybeUser: { user?: User | null }): void { if (objWithMaybeUser.user?.ip_address === undefined) { diff --git a/packages/core/test/lib/envelope.test.ts b/packages/core/test/lib/envelope.test.ts index d0882524dc67..c5d246973842 100644 --- a/packages/core/test/lib/envelope.test.ts +++ b/packages/core/test/lib/envelope.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { DsnComponents } from '../../build/types/types-hoist/dsn'; import type { DynamicSamplingContext } from '../../build/types/types-hoist/envelope'; -import type { Client } from '../../src'; +import type { Client, SdkInfo } from '../../src'; import { getCurrentScope, getIsolationScope, @@ -10,7 +10,7 @@ import { setAsyncContextStrategy, setCurrentClient, } from '../../src'; -import { createEventEnvelope, createSpanEnvelope } from '../../src/envelope'; +import { _enhanceEventWithSdkInfo, createEventEnvelope, createSpanEnvelope } from '../../src/envelope'; import type { Event } from '../../src/types-hoist/event'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; @@ -261,3 +261,139 @@ describe('createSpanEnvelope', () => { }); }); }); + +describe('_enhanceEventWithSdkInfo', () => { + it('does nothing if no new sdk info is provided', () => { + const event: Event = { + sdk: { name: 'original', version: '1.0.0' }, + }; + const enhancedEvent = _enhanceEventWithSdkInfo(event, undefined); + expect(enhancedEvent.sdk).toEqual({ name: 'original', version: '1.0.0' }); + }); + + /** + * Note LS: I'm not sure if this is intended behaviour, but this is how it was before + * I made implementation changes for the `settings` object. Documenting behaviour for now, + * we can revisit it if it turns out this is not intended. + */ + it('prefers original version and name over newSdkInfo', () => { + const event: Event = { + sdk: { + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2'], + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + }, + }; + const newSdkInfo: SdkInfo = { name: 'newName', version: '2.0.0' }; + + const enhancedEvent = _enhanceEventWithSdkInfo(event, newSdkInfo); + + expect(enhancedEvent.sdk).toEqual({ + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2'], + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + }); + }); + + describe('integrations and packages', () => { + it('merges integrations and packages of original and newSdkInfo', () => { + const event: Event = { + sdk: { + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2'], + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + }, + }; + + const newSdkInfo: SdkInfo = { + name: 'newName', + version: '2.0.0', + integrations: ['integration3', 'integration4'], + packages: [{ name: '@sentry/node', version: '11.0.0' }], + }; + + const enhancedEvent = _enhanceEventWithSdkInfo(event, newSdkInfo); + + expect(enhancedEvent.sdk).toEqual({ + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2', 'integration3', 'integration4'], + packages: [ + { name: '@sentry/browser', version: '10.0.0' }, + { name: '@sentry/node', version: '11.0.0' }, + ], + }); + }); + + it('creates empty integrations and packages arrays if no original or newSdkInfo are provided', () => { + const event: Event = { + sdk: { + name: 'original', + version: '1.0.0', + }, + }; + + const newSdkInfo: SdkInfo = {}; + + const enhancedEvent = _enhanceEventWithSdkInfo(event, newSdkInfo); + expect(enhancedEvent.sdk).toEqual({ + name: 'original', + version: '1.0.0', + integrations: [], + packages: [], + }); + }); + }); + + describe('settings', () => { + it('prefers newSdkInfo settings over original settings', () => { + const event: Event = { + sdk: { + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2'], + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + settings: { infer_ip: 'auto' }, + }, + }; + const newSdkInfo: SdkInfo = { + settings: { infer_ip: 'never' }, + }; + + const enhancedEvent = _enhanceEventWithSdkInfo(event, newSdkInfo); + + expect(enhancedEvent.sdk).toEqual({ + name: 'original', + version: '1.0.0', + integrations: ['integration1', 'integration2'], + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + settings: { infer_ip: 'never' }, + }); + }); + + it("doesn't create a `settings` object if no settings are provided", () => { + const event: Event = { + sdk: { + name: 'original', + version: '1.0.0', + }, + }; + + const newSdkInfo: SdkInfo = { + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + }; + + const enhancedEvent = _enhanceEventWithSdkInfo(event, newSdkInfo); + expect(enhancedEvent.sdk).toEqual({ + name: 'original', + version: '1.0.0', + packages: [{ name: '@sentry/browser', version: '10.0.0' }], + integrations: [], + settings: undefined, // undefined is fine because JSON.stringify omits undefined values anyways + }); + }); + }); +}); diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 4ecd436b709a..1975487363f7 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -65,6 +65,9 @@ describe('Client init()', () => { version: expect.any(String), }, ], + settings: { + infer_ip: 'never', + }, }, }, environment: 'test', diff --git a/packages/nuxt/test/client/sdk.test.ts b/packages/nuxt/test/client/sdk.test.ts index 1a79cfe445a7..29448c720ea4 100644 --- a/packages/nuxt/test/client/sdk.test.ts +++ b/packages/nuxt/test/client/sdk.test.ts @@ -27,6 +27,9 @@ describe('Nuxt Client SDK', () => { { name: 'npm:@sentry/nuxt', version: SDK_VERSION }, { name: 'npm:@sentry/vue', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }; diff --git a/packages/react-router/test/client/sdk.test.ts b/packages/react-router/test/client/sdk.test.ts index d6767ccfff23..46c629a0bbd5 100644 --- a/packages/react-router/test/client/sdk.test.ts +++ b/packages/react-router/test/client/sdk.test.ts @@ -33,6 +33,9 @@ describe('React Router client SDK', () => { { name: 'npm:@sentry/react-router', version: SDK_VERSION }, { name: 'npm:@sentry/browser', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }; diff --git a/packages/remix/test/index.client.test.ts b/packages/remix/test/index.client.test.ts index 2497ce281694..c5af95a420ef 100644 --- a/packages/remix/test/index.client.test.ts +++ b/packages/remix/test/index.client.test.ts @@ -36,6 +36,9 @@ describe('Client init()', () => { version: expect.any(String), }, ], + settings: { + infer_ip: 'never', + }, }, }, }), diff --git a/packages/replay-internal/src/util/prepareReplayEvent.ts b/packages/replay-internal/src/util/prepareReplayEvent.ts index 7de2cff212c0..e9cdd050e122 100644 --- a/packages/replay-internal/src/util/prepareReplayEvent.ts +++ b/packages/replay-internal/src/util/prepareReplayEvent.ts @@ -49,12 +49,13 @@ export async function prepareReplayEvent({ // extract the SDK name because `client._prepareEvent` doesn't add it to the event const metadata = client.getSdkMetadata(); - const { name, version } = metadata?.sdk || {}; + const { name, version, settings } = metadata?.sdk || {}; preparedEvent.sdk = { ...preparedEvent.sdk, name: name || 'sentry.javascript.unknown', version: version || '0.0.0', + settings, }; return preparedEvent; diff --git a/packages/solid/test/sdk.test.ts b/packages/solid/test/sdk.test.ts index dec8220668a8..cbef43425a32 100644 --- a/packages/solid/test/sdk.test.ts +++ b/packages/solid/test/sdk.test.ts @@ -21,6 +21,9 @@ describe('Initialize Solid SDK', () => { name: 'sentry.javascript.solid', packages: [{ name: 'npm:@sentry/solid', version: SDK_VERSION }], version: SDK_VERSION, + settings: { + infer_ip: 'never', + }, }, }, }; diff --git a/packages/solidstart/test/client/sdk.test.ts b/packages/solidstart/test/client/sdk.test.ts index 73bb412d1909..159cc6da8bcb 100644 --- a/packages/solidstart/test/client/sdk.test.ts +++ b/packages/solidstart/test/client/sdk.test.ts @@ -25,6 +25,9 @@ describe('Initialize Solid Start SDK', () => { { name: 'npm:@sentry/solid', version: SDK_VERSION }, ], version: SDK_VERSION, + settings: { + infer_ip: 'never', + }, }, }, }; diff --git a/packages/svelte/test/sdk.test.ts b/packages/svelte/test/sdk.test.ts index 725d9bc66898..665805b95714 100644 --- a/packages/svelte/test/sdk.test.ts +++ b/packages/svelte/test/sdk.test.ts @@ -25,6 +25,9 @@ describe('Initialize Svelte SDk', () => { name: 'sentry.javascript.svelte', packages: [{ name: 'npm:@sentry/svelte', version: SDK_VERSION }], version: SDK_VERSION, + settings: { + infer_ip: 'never', + }, }, }, }; @@ -44,6 +47,9 @@ describe('Initialize Svelte SDk', () => { { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, { name: 'npm:@sentry/svelte', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }); @@ -59,6 +65,9 @@ describe('Initialize Svelte SDk', () => { { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, { name: 'npm:@sentry/svelte', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }), diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts index 1bbd2e2bc81f..91bc44d77b21 100644 --- a/packages/sveltekit/test/client/sdk.test.ts +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -33,6 +33,9 @@ describe('Sentry client SDK', () => { { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, { name: 'npm:@sentry/svelte', version: SDK_VERSION }, ], + settings: { + infer_ip: 'never', + }, }, }, }),