Skip to content

Commit 213bb4a

Browse files
authored
feat(core): Add getIsolationScope() method (#9957)
Also ensure to actually export `getGlobalScope()` everywhere as well. The isolation scope, currently, lies on the hub, and is applied to all events.
1 parent a04e780 commit 213bb4a

File tree

26 files changed

+274
-71
lines changed

26 files changed

+274
-71
lines changed

packages/astro/src/index.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export {
2727
getCurrentHub,
2828
getClient,
2929
getCurrentScope,
30+
getGlobalScope,
31+
getIsolationScope,
3032
Hub,
3133
makeMain,
3234
Scope,

packages/bun/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export {
4444
getCurrentHub,
4545
getClient,
4646
getCurrentScope,
47+
getGlobalScope,
48+
getIsolationScope,
4749
Hub,
4850
lastEventId,
4951
makeMain,

packages/core/src/baseclient.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
4949
import { DEBUG_BUILD } from './debug-build';
5050
import { createEventEnvelope, createSessionEnvelope } from './envelope';
5151
import { getClient } from './exports';
52+
import { getIsolationScope } from './hub';
5253
import type { IntegrationIndex } from './integration';
5354
import { setupIntegration, setupIntegrations } from './integration';
5455
import { createMetricEnvelope } from './metrics/envelope';
@@ -588,7 +589,12 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
588589
* @param scope A scope containing event metadata.
589590
* @returns A new event with more information.
590591
*/
591-
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
592+
protected _prepareEvent(
593+
event: Event,
594+
hint: EventHint,
595+
scope?: Scope,
596+
isolationScope = getIsolationScope(),
597+
): PromiseLike<Event | null> {
592598
const options = this.getOptions();
593599
const integrations = Object.keys(this._integrations);
594600
if (!hint.integrations && integrations.length > 0) {
@@ -597,7 +603,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
597603

598604
this.emit('preprocessEvent', event, hint);
599605

600-
return prepareEvent(options, event, hint, scope, this).then(evt => {
606+
return prepareEvent(options, event, hint, scope, this, isolationScope).then(evt => {
601607
if (evt === null) {
602608
return evt;
603609
}

packages/core/src/hub.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export class Hub implements HubInterface {
104104
/** Contains the last event id of a captured event. */
105105
private _lastEventId?: string;
106106

107+
private _isolationScope: Scope;
108+
107109
/**
108110
* Creates a new instance of the hub, will push one {@link Layer} into the
109111
* internal stack on creation.
@@ -112,11 +114,18 @@ export class Hub implements HubInterface {
112114
* @param scope bound to the hub.
113115
* @param version number, higher number means higher priority.
114116
*/
115-
public constructor(client?: Client, scope: Scope = new Scope(), private readonly _version: number = API_VERSION) {
117+
public constructor(
118+
client?: Client,
119+
scope: Scope = new Scope(),
120+
isolationScope = new Scope(),
121+
private readonly _version: number = API_VERSION,
122+
) {
116123
this._stack = [{ scope }];
117124
if (client) {
118125
this.bindClient(client);
119126
}
127+
128+
this._isolationScope = isolationScope;
120129
}
121130

122131
/**
@@ -188,6 +197,11 @@ export class Hub implements HubInterface {
188197
return this.getStackTop().scope;
189198
}
190199

200+
/** @inheritdoc */
201+
public getIsolationScope(): Scope {
202+
return this._isolationScope;
203+
}
204+
191205
/** Returns the scope stack for domains or the process. */
192206
public getStack(): Layer[] {
193207
return this._stack;
@@ -567,6 +581,15 @@ export function getCurrentHub(): Hub {
567581
return getGlobalHub(registry);
568582
}
569583

584+
/**
585+
* Get the currently active isolation scope.
586+
* The isolation scope is active for the current exection context,
587+
* meaning that it will remain stable for the same Hub.
588+
*/
589+
export function getIsolationScope(): Scope {
590+
return getCurrentHub().getIsolationScope();
591+
}
592+
570593
function getGlobalHub(registry: Carrier = getMainCarrier()): Hub {
571594
// If there's no hub, or its an old API, assign a new one
572595
if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
@@ -585,8 +608,10 @@ function getGlobalHub(registry: Carrier = getMainCarrier()): Hub {
585608
export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub()): void {
586609
// If there's no hub on current domain, or it's an old API, assign a new one
587610
if (!hasHubOnCarrier(carrier) || getHubFromCarrier(carrier).isOlderThan(API_VERSION)) {
588-
const globalHubTopStack = parent.getStackTop();
589-
setHubOnCarrier(carrier, new Hub(globalHubTopStack.client, globalHubTopStack.scope.clone()));
611+
const client = parent.getClient();
612+
const scope = parent.getScope();
613+
const isolationScope = parent.getIsolationScope();
614+
setHubOnCarrier(carrier, new Hub(client, scope.clone(), isolationScope.clone()));
590615
}
591616
}
592617

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export {
3131
} from './exports';
3232
export {
3333
getCurrentHub,
34+
getIsolationScope,
3435
getHubFromCarrier,
3536
Hub,
3637
makeMain,

packages/core/src/server-runtime-client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ export class ServerRuntimeClient<
216216
/**
217217
* @inheritDoc
218218
*/
219-
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
219+
protected _prepareEvent(
220+
event: Event,
221+
hint: EventHint,
222+
scope?: Scope,
223+
isolationScope?: Scope,
224+
): PromiseLike<Event | null> {
220225
if (this._options.platform) {
221226
event.platform = event.platform || this._options.platform;
222227
}
@@ -232,7 +237,7 @@ export class ServerRuntimeClient<
232237
event.server_name = event.server_name || this._options.serverName;
233238
}
234239

235-
return super._prepareEvent(event, hint, scope);
240+
return super._prepareEvent(event, hint, scope, isolationScope);
236241
}
237242

238243
/** Extract trace information from scope */

packages/core/src/utils/prepareEvent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function prepareEvent(
4848
hint: EventHint,
4949
scope?: Scope,
5050
client?: Client,
51+
isolationScope?: Scope,
5152
): PromiseLike<Event | null> {
5253
const { normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = options;
5354
const prepared: Event = {
@@ -80,6 +81,11 @@ export function prepareEvent(
8081
// Merge scope data together
8182
const data = getGlobalScope().getScopeData();
8283

84+
if (isolationScope) {
85+
const isolationData = isolationScope.getScopeData();
86+
mergeScopeData(data, isolationData);
87+
}
88+
8389
if (finalScope) {
8490
const finalScopeData = finalScope.getScopeData();
8591
mergeScopeData(data, finalScopeData);

packages/core/test/lib/prepareEvent.test.ts

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
ScopeContext,
1010
} from '@sentry/types';
1111
import { GLOBAL_OBJ, createStackParser } from '@sentry/utils';
12-
import { setGlobalScope } from '../../src';
12+
import { getCurrentHub, getIsolationScope, setGlobalScope } from '../../src';
1313

1414
import { Scope, getGlobalScope } from '../../src/scope';
1515
import {
@@ -192,6 +192,7 @@ describe('parseEventHintOrCaptureContext', () => {
192192
describe('prepareEvent', () => {
193193
beforeEach(() => {
194194
setGlobalScope(undefined);
195+
getCurrentHub().getIsolationScope().clear();
195196
});
196197

197198
it('works without any scope data', async () => {
@@ -240,12 +241,15 @@ describe('prepareEvent', () => {
240241
const breadcrumb1 = { message: '1', timestamp: 111 } as Breadcrumb;
241242
const breadcrumb2 = { message: '2', timestamp: 222 } as Breadcrumb;
242243
const breadcrumb3 = { message: '3', timestamp: 123 } as Breadcrumb;
244+
const breadcrumb4 = { message: '4', timestamp: 123 } as Breadcrumb;
243245

244246
const eventProcessor1 = jest.fn((a: unknown) => a) as EventProcessor;
245247
const eventProcessor2 = jest.fn((b: unknown) => b) as EventProcessor;
248+
const eventProcessor3 = jest.fn((b: unknown) => b) as EventProcessor;
246249

247250
const attachment1 = { filename: '1' } as Attachment;
248251
const attachment2 = { filename: '2' } as Attachment;
252+
const attachment3 = { filename: '3' } as Attachment;
249253

250254
const scope = new Scope();
251255
scope.update({
@@ -261,13 +265,19 @@ describe('prepareEvent', () => {
261265
scope.addAttachment(attachment1);
262266

263267
const globalScope = getGlobalScope();
268+
const isolationScope = getIsolationScope();
264269

265270
globalScope.addBreadcrumb(breadcrumb2);
266271
globalScope.addEventProcessor(eventProcessor2);
267272
globalScope.setSDKProcessingMetadata({ aa: 'aa' });
268273
globalScope.addAttachment(attachment2);
269274

270-
const event = { message: 'foo', breadcrumbs: [breadcrumb3], fingerprint: ['dd'] };
275+
isolationScope.addBreadcrumb(breadcrumb3);
276+
isolationScope.addEventProcessor(eventProcessor3);
277+
isolationScope.setSDKProcessingMetadata({ bb: 'bb' });
278+
isolationScope.addAttachment(attachment3);
279+
280+
const event = { message: 'foo', breadcrumbs: [breadcrumb4], fingerprint: ['dd'] };
271281

272282
const options = {} as ClientOptions;
273283
const processedEvent = await prepareEvent(
@@ -277,15 +287,18 @@ describe('prepareEvent', () => {
277287
integrations: [],
278288
},
279289
scope,
290+
undefined,
291+
isolationScope,
280292
);
281293

282294
expect(eventProcessor1).toHaveBeenCalledTimes(1);
283295
expect(eventProcessor2).toHaveBeenCalledTimes(1);
296+
expect(eventProcessor3).toHaveBeenCalledTimes(1);
284297

285298
// Test that attachments are correctly merged
286299
expect(eventProcessor1).toHaveBeenCalledWith(processedEvent, {
287300
integrations: [],
288-
attachments: [attachment2, attachment1],
301+
attachments: [attachment2, attachment3, attachment1],
289302
});
290303

291304
expect(processedEvent).toEqual({
@@ -298,9 +311,10 @@ describe('prepareEvent', () => {
298311
extra: { extra1: 'aa', extra2: 'aa' },
299312
contexts: { os: { name: 'os1' }, culture: { display_name: 'name1' } },
300313
fingerprint: ['dd', 'aa'],
301-
breadcrumbs: [breadcrumb3, breadcrumb2, breadcrumb1],
314+
breadcrumbs: [breadcrumb4, breadcrumb2, breadcrumb3, breadcrumb1],
302315
sdkProcessingMetadata: {
303316
aa: 'aa',
317+
bb: 'bb',
304318
propagationContext: {
305319
spanId: '1',
306320
traceId: '1',
@@ -312,33 +326,50 @@ describe('prepareEvent', () => {
312326
it('works without a scope', async () => {
313327
const breadcrumb1 = { message: '1', timestamp: 111 } as Breadcrumb;
314328
const breadcrumb2 = { message: '2', timestamp: 222 } as Breadcrumb;
329+
const breadcrumb3 = { message: '3', timestamp: 333 } as Breadcrumb;
315330

316331
const eventProcessor1 = jest.fn((a: unknown) => a) as EventProcessor;
332+
const eventProcessor2 = jest.fn((a: unknown) => a) as EventProcessor;
317333

318-
const attachment1 = { filename: '1' } as Attachment;
319-
const attachment2 = { filename: '2' } as Attachment;
334+
const attachmentGlobal = { filename: 'global scope attachment' } as Attachment;
335+
const attachmentIsolation = { filename: 'isolation scope attachment' } as Attachment;
336+
const attachmentHint = { filename: 'hint attachment' } as Attachment;
320337

321338
const globalScope = getGlobalScope();
339+
const isolationScope = getIsolationScope();
322340

323341
globalScope.addBreadcrumb(breadcrumb1);
324342
globalScope.addEventProcessor(eventProcessor1);
325343
globalScope.setSDKProcessingMetadata({ aa: 'aa' });
326-
globalScope.addAttachment(attachment1);
344+
globalScope.addAttachment(attachmentGlobal);
327345

328-
const event = { message: 'foo', breadcrumbs: [breadcrumb2], fingerprint: ['dd'] };
346+
isolationScope.addBreadcrumb(breadcrumb2);
347+
isolationScope.addEventProcessor(eventProcessor2);
348+
isolationScope.setSDKProcessingMetadata({ bb: 'bb' });
349+
isolationScope.addAttachment(attachmentIsolation);
350+
351+
const event = { message: 'foo', breadcrumbs: [breadcrumb3], fingerprint: ['dd'] };
329352

330353
const options = {} as ClientOptions;
331-
const processedEvent = await prepareEvent(options, event, {
332-
integrations: [],
333-
attachments: [attachment2],
334-
});
354+
const processedEvent = await prepareEvent(
355+
options,
356+
event,
357+
{
358+
integrations: [],
359+
attachments: [attachmentHint],
360+
},
361+
undefined,
362+
undefined,
363+
isolationScope,
364+
);
335365

336366
expect(eventProcessor1).toHaveBeenCalledTimes(1);
367+
expect(eventProcessor2).toHaveBeenCalledTimes(1);
337368

338369
// Test that attachments are correctly merged
339370
expect(eventProcessor1).toHaveBeenCalledWith(processedEvent, {
340371
integrations: [],
341-
attachments: [attachment2, attachment1],
372+
attachments: [attachmentHint, attachmentGlobal, attachmentIsolation],
342373
});
343374

344375
expect(processedEvent).toEqual({
@@ -347,10 +378,11 @@ describe('prepareEvent', () => {
347378
environment: 'production',
348379
message: 'foo',
349380
fingerprint: ['dd'],
350-
breadcrumbs: [breadcrumb2, breadcrumb1],
381+
breadcrumbs: [breadcrumb3, breadcrumb1, breadcrumb2],
351382
sdkProcessingMetadata: {
352383
aa: 'aa',
353-
propagationContext: globalScope.getPropagationContext(),
384+
bb: 'bb',
385+
propagationContext: isolationScope.getPropagationContext(),
354386
},
355387
});
356388
});

packages/deno/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export {
4343
getCurrentHub,
4444
getClient,
4545
getCurrentScope,
46+
getGlobalScope,
47+
getIsolationScope,
4648
Hub,
4749
lastEventId,
4850
makeMain,

packages/feedback/src/util/prepareFeedbackEvent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Scope } from '@sentry/core';
2+
import { getIsolationScope } from '@sentry/core';
23
import { prepareEvent } from '@sentry/core';
34
import type { Client, FeedbackEvent } from '@sentry/types';
45

@@ -26,6 +27,7 @@ export async function prepareFeedbackEvent({
2627
eventHint,
2728
scope,
2829
client,
30+
getIsolationScope(),
2931
)) as FeedbackEvent | null;
3032

3133
if (preparedEvent === null) {

packages/hub/test/global.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ describe('global', () => {
1919
});
2020

2121
test('getGlobalHub', () => {
22-
const newestHub = new Hub(undefined, undefined, 999999);
22+
const newestHub = new Hub(undefined, undefined, undefined, 999999);
2323
GLOBAL_OBJ.__SENTRY__.hub = newestHub;
2424
expect(getCurrentHub()).toBe(newestHub);
2525
});
2626

2727
test('hub extension methods receive correct hub instance', () => {
28-
const newestHub = new Hub(undefined, undefined, 999999);
28+
const newestHub = new Hub(undefined, undefined, undefined, 999999);
2929
GLOBAL_OBJ.__SENTRY__.hub = newestHub;
3030
const fn = jest.fn().mockImplementation(function (...args: []) {
3131
// @ts-expect-error typescript complains that this can be `any`

0 commit comments

Comments
 (0)