Skip to content

Commit e0785ea

Browse files
authored
feat(core)!: Update hasTracingEnabled to consider empty trace config (#14857)
This PR updates the behavior of passing `undefined` to `tracesSampleRate` (or `enabledTracing`). now, this will _not_ trigger TWP, but tracing will be disabled. If you really want TWP, you need to configure `tracesSampleRate: 0`. Closes #13262
1 parent 458dca4 commit e0785ea

File tree

7 files changed

+136
-62
lines changed

7 files changed

+136
-62
lines changed

docs/migration/v8-to-v9.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ Sentry.init({
6868
});
6969
```
7070

71+
- In previous versions, we determined if tracing is enabled (for Tracing Without Performance) by checking if either `tracesSampleRate` or `traceSampler` are _defined_ at all, in `Sentry.init()`. This means that e.g. the following config would lead to tracing without performance (=tracing being enabled, even if no spans would be started):
72+
73+
```js
74+
Sentry.init({
75+
tracesSampleRate: undefined,
76+
});
77+
```
78+
79+
In v9, an `undefined` value will be treated the same as if the value is not defined at all. You'll need to set `tracesSampleRate: 0` if you want to enable tracing without performance.
80+
7181
### `@sentry/node`
7282

7383
- When `skipOpenTelemetrySetup: true` is configured, `httpIntegration({ spans: false })` will be configured by default. This means that you no longer have to specify this yourself in this scenario. With this change, no spans are emitted once `skipOpenTelemetrySetup: true` is configured, without any further configuration being needed.

packages/browser/src/sdk.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Client, DsnLike, Integration, Options } from '@sentry/core';
12
import {
23
consoleSandbox,
34
dedupeIntegration,
@@ -12,7 +13,6 @@ import {
1213
stackParserFromStackParserOptions,
1314
supportsFetch,
1415
} from '@sentry/core';
15-
import type { Client, DsnLike, Integration, Options } from '@sentry/core';
1616
import type { BrowserClientOptions, BrowserOptions } from './client';
1717
import { BrowserClient } from './client';
1818
import { DEBUG_BUILD } from './debug-build';
@@ -51,7 +51,8 @@ export function getDefaultIntegrations(options: Options): Integration[] {
5151
return integrations;
5252
}
5353

54-
function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
54+
/** Exported only for tests. */
55+
export function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
5556
const defaultOptions: BrowserOptions = {
5657
defaultIntegrations: getDefaultIntegrations(optionsArg),
5758
release:
@@ -64,15 +65,27 @@ function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
6465
sendClientReports: true,
6566
};
6667

67-
// TODO: Instead of dropping just `defaultIntegrations`, we should simply
68-
// call `dropUndefinedKeys` on the entire `optionsArg`.
69-
// However, for this to work we need to adjust the `hasTracingEnabled()` logic
70-
// first as it differentiates between `undefined` and the key not being in the object.
71-
if (optionsArg.defaultIntegrations == null) {
72-
delete optionsArg.defaultIntegrations;
68+
return {
69+
...defaultOptions,
70+
...dropTopLevelUndefinedKeys(optionsArg),
71+
};
72+
}
73+
74+
/**
75+
* In contrast to the regular `dropUndefinedKeys` method,
76+
* this one does not deep-drop keys, but only on the top level.
77+
*/
78+
function dropTopLevelUndefinedKeys<T extends object>(obj: T): Partial<T> {
79+
const mutatetedObj: Partial<T> = {};
80+
81+
for (const k of Object.getOwnPropertyNames(obj)) {
82+
const key = k as keyof T;
83+
if (obj[key] !== undefined) {
84+
mutatetedObj[key] = obj[key];
85+
}
7386
}
7487

75-
return { ...defaultOptions, ...optionsArg };
88+
return mutatetedObj;
7689
}
7790

7891
type ExtensionProperties = {

packages/browser/test/sdk.test.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { Client, Integration } from '@sentry/core';
1313

1414
import type { BrowserOptions } from '../src';
1515
import { WINDOW } from '../src';
16-
import { init } from '../src/sdk';
16+
import { applyDefaultOptions, getDefaultIntegrations, init } from '../src/sdk';
1717

1818
const PUBLIC_DSN = 'https://username@domain/123';
1919

@@ -277,3 +277,100 @@ describe('init', () => {
277277
expect(client).not.toBeUndefined();
278278
});
279279
});
280+
281+
describe('applyDefaultOptions', () => {
282+
test('it works with empty options', () => {
283+
const options = {};
284+
const actual = applyDefaultOptions(options);
285+
286+
expect(actual).toEqual({
287+
defaultIntegrations: expect.any(Array),
288+
release: undefined,
289+
autoSessionTracking: true,
290+
sendClientReports: true,
291+
});
292+
293+
expect(actual.defaultIntegrations && actual.defaultIntegrations.map(i => i.name)).toEqual(
294+
getDefaultIntegrations(options).map(i => i.name),
295+
);
296+
});
297+
298+
test('it works with options', () => {
299+
const options = {
300+
tracesSampleRate: 0.5,
301+
release: '1.0.0',
302+
autoSessionTracking: false,
303+
};
304+
const actual = applyDefaultOptions(options);
305+
306+
expect(actual).toEqual({
307+
defaultIntegrations: expect.any(Array),
308+
release: '1.0.0',
309+
autoSessionTracking: false,
310+
sendClientReports: true,
311+
tracesSampleRate: 0.5,
312+
});
313+
314+
expect(actual.defaultIntegrations && actual.defaultIntegrations.map(i => i.name)).toEqual(
315+
getDefaultIntegrations(options).map(i => i.name),
316+
);
317+
});
318+
319+
test('it works with defaultIntegrations=false', () => {
320+
const options = {
321+
defaultIntegrations: false,
322+
} as const;
323+
const actual = applyDefaultOptions(options);
324+
325+
expect(actual.defaultIntegrations).toStrictEqual(false);
326+
});
327+
328+
test('it works with defaultIntegrations=[]', () => {
329+
const options = {
330+
defaultIntegrations: [],
331+
};
332+
const actual = applyDefaultOptions(options);
333+
334+
expect(actual.defaultIntegrations).toEqual([]);
335+
});
336+
337+
test('it works with tracesSampleRate=undefined', () => {
338+
const options = {
339+
tracesSampleRate: undefined,
340+
} as const;
341+
const actual = applyDefaultOptions(options);
342+
343+
// Not defined, not even undefined
344+
expect('tracesSampleRate' in actual).toBe(false);
345+
});
346+
347+
test('it works with tracesSampleRate=null', () => {
348+
const options = {
349+
tracesSampleRate: null,
350+
} as any;
351+
const actual = applyDefaultOptions(options);
352+
353+
expect(actual.tracesSampleRate).toStrictEqual(null);
354+
});
355+
356+
test('it works with tracesSampleRate=0', () => {
357+
const options = {
358+
tracesSampleRate: 0,
359+
} as const;
360+
const actual = applyDefaultOptions(options);
361+
362+
expect(actual.tracesSampleRate).toStrictEqual(0);
363+
});
364+
365+
test('it does not deep-drop undefined keys', () => {
366+
const options = {
367+
obj: {
368+
prop: undefined,
369+
},
370+
} as any;
371+
const actual = applyDefaultOptions(options) as any;
372+
373+
expect('prop' in actual.obj).toBe(true);
374+
expect(actual.obj.prop).toStrictEqual(undefined);
375+
});
376+
});

packages/core/src/baseclient.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { dsnToString, makeDsn } from './utils-hoist/dsn';
4747
import { addItemToEnvelope, createAttachmentEnvelopeItem } from './utils-hoist/envelope';
4848
import { SentryError } from './utils-hoist/error';
4949
import { isParameterizedString, isPlainObject, isPrimitive, isThenable } from './utils-hoist/is';
50-
import { consoleSandbox, logger } from './utils-hoist/logger';
50+
import { logger } from './utils-hoist/logger';
5151
import { checkOrSetAlreadyCaught, uuid4 } from './utils-hoist/misc';
5252
import { SyncPromise, rejectedSyncPromise, resolvedSyncPromise } from './utils-hoist/syncpromise';
5353
import { getPossibleEventMessages } from './utils/eventUtils';
@@ -144,18 +144,6 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
144144
url,
145145
});
146146
}
147-
148-
// TODO(v9): Remove this deprecation warning
149-
const tracingOptions = ['enableTracing', 'tracesSampleRate', 'tracesSampler'] as const;
150-
const undefinedOption = tracingOptions.find(option => option in options && options[option] == undefined);
151-
if (undefinedOption) {
152-
consoleSandbox(() => {
153-
// eslint-disable-next-line no-console
154-
console.warn(
155-
`[Sentry] Deprecation warning: \`${undefinedOption}\` is set to undefined, which leads to tracing being enabled. In v9, a value of \`undefined\` will result in tracing being disabled.`,
156-
);
157-
});
158-
}
159147
}
160148

161149
/**

packages/core/src/utils/hasTracingEnabled.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ export function hasTracingEnabled(
1919
const client = getClient();
2020
const options = maybeOptions || (client && client.getOptions());
2121
// eslint-disable-next-line deprecation/deprecation
22-
return !!options && (options.enableTracing || 'tracesSampleRate' in options || 'tracesSampler' in options);
22+
return !!options && (options.enableTracing || options.tracesSampleRate != null || !!options.tracesSampler);
2323
}

packages/core/test/lib/baseclient.test.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -87,44 +87,6 @@ describe('BaseClient', () => {
8787
expect(consoleWarnSpy).toHaveBeenCalledTimes(0);
8888
consoleWarnSpy.mockRestore();
8989
});
90-
91-
describe.each(['tracesSampleRate', 'tracesSampler', 'enableTracing'])('%s', key => {
92-
it('warns when set to undefined', () => {
93-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
94-
95-
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, [key]: undefined });
96-
new TestClient(options);
97-
98-
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
99-
expect(consoleWarnSpy).toBeCalledWith(
100-
`[Sentry] Deprecation warning: \`${key}\` is set to undefined, which leads to tracing being enabled. In v9, a value of \`undefined\` will result in tracing being disabled.`,
101-
);
102-
consoleWarnSpy.mockRestore();
103-
});
104-
105-
it('warns when set to null', () => {
106-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
107-
108-
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, [key]: null });
109-
new TestClient(options);
110-
111-
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
112-
expect(consoleWarnSpy).toBeCalledWith(
113-
`[Sentry] Deprecation warning: \`${key}\` is set to undefined, which leads to tracing being enabled. In v9, a value of \`undefined\` will result in tracing being disabled.`,
114-
);
115-
consoleWarnSpy.mockRestore();
116-
});
117-
118-
it('does not warn when set to 0', () => {
119-
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
120-
121-
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, [key]: 0 });
122-
new TestClient(options);
123-
124-
expect(consoleWarnSpy).toHaveBeenCalledTimes(0);
125-
consoleWarnSpy.mockRestore();
126-
});
127-
});
12890
});
12991

13092
describe('getOptions()', () => {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ describe('hasTracingEnabled', () => {
1010
['With tracesSampleRate', { tracesSampleRate }, true],
1111
['With enableTracing=true', { enableTracing: true }, true],
1212
['With enableTracing=false', { enableTracing: false }, false],
13+
['With enableTracing=undefined', { enableTracing: undefined }, false],
14+
['With tracesSampleRate=undefined', { tracesSampleRate: undefined }, false],
15+
['With tracesSampleRate=0', { tracesSampleRate: 0 }, true],
16+
['With tracesSampler=undefined', { tracesSampler: undefined }, false],
1317
['With tracesSampler && enableTracing=false', { tracesSampler, enableTracing: false }, true],
1418
['With tracesSampleRate && enableTracing=false', { tracesSampler, enableTracing: false }, true],
1519
['With tracesSampler and tracesSampleRate', { tracesSampler, tracesSampleRate }, true],

0 commit comments

Comments
 (0)