Skip to content

feat(core): Add getIsolationScope() method #9957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export {
getCurrentHub,
getClient,
getCurrentScope,
getGlobalScope,
getIsolationScope,
Hub,
makeMain,
Scope,
Expand Down
2 changes: 2 additions & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export {
getCurrentHub,
getClient,
getCurrentScope,
getGlobalScope,
getIsolationScope,
Hub,
lastEventId,
makeMain,
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
import { DEBUG_BUILD } from './debug-build';
import { createEventEnvelope, createSessionEnvelope } from './envelope';
import { getClient } from './exports';
import { getIsolationScope } from './hub';
import type { IntegrationIndex } from './integration';
import { setupIntegration, setupIntegrations } from './integration';
import { createMetricEnvelope } from './metrics/envelope';
Expand Down Expand Up @@ -588,7 +589,12 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
* @param scope A scope containing event metadata.
* @returns A new event with more information.
*/
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
protected _prepareEvent(
event: Event,
hint: EventHint,
scope?: Scope,
isolationScope = getIsolationScope(),
): PromiseLike<Event | null> {
const options = this.getOptions();
const integrations = Object.keys(this._integrations);
if (!hint.integrations && integrations.length > 0) {
Expand All @@ -597,7 +603,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {

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

return prepareEvent(options, event, hint, scope, this).then(evt => {
return prepareEvent(options, event, hint, scope, this, isolationScope).then(evt => {
if (evt === null) {
return evt;
}
Expand Down
31 changes: 28 additions & 3 deletions packages/core/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export class Hub implements HubInterface {
/** Contains the last event id of a captured event. */
private _lastEventId?: string;

private _isolationScope: Scope;

/**
* Creates a new instance of the hub, will push one {@link Layer} into the
* internal stack on creation.
Expand All @@ -112,11 +114,18 @@ export class Hub implements HubInterface {
* @param scope bound to the hub.
* @param version number, higher number means higher priority.
*/
public constructor(client?: Client, scope: Scope = new Scope(), private readonly _version: number = API_VERSION) {
public constructor(
client?: Client,
scope: Scope = new Scope(),
isolationScope = new Scope(),
private readonly _version: number = API_VERSION,
) {
this._stack = [{ scope }];
if (client) {
this.bindClient(client);
}

this._isolationScope = isolationScope;
}

/**
Expand Down Expand Up @@ -188,6 +197,11 @@ export class Hub implements HubInterface {
return this.getStackTop().scope;
}

/** @inheritdoc */
public getIsolationScope(): Scope {
return this._isolationScope;
}
Comment on lines +201 to +203
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a getter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't really have getters there right now, also this is really just a very short-lived thing anyhow and will be gone by v8 😅


/** Returns the scope stack for domains or the process. */
public getStack(): Layer[] {
return this._stack;
Expand Down Expand Up @@ -567,6 +581,15 @@ export function getCurrentHub(): Hub {
return getGlobalHub(registry);
}

/**
* Get the currently active isolation scope.
* The isolation scope is active for the current exection context,
* meaning that it will remain stable for the same Hub.
*/
export function getIsolationScope(): Scope {
return getCurrentHub().getIsolationScope();
}

function getGlobalHub(registry: Carrier = getMainCarrier()): Hub {
// If there's no hub, or its an old API, assign a new one
if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
Expand All @@ -585,8 +608,10 @@ function getGlobalHub(registry: Carrier = getMainCarrier()): Hub {
export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub()): void {
// If there's no hub on current domain, or it's an old API, assign a new one
if (!hasHubOnCarrier(carrier) || getHubFromCarrier(carrier).isOlderThan(API_VERSION)) {
const globalHubTopStack = parent.getStackTop();
setHubOnCarrier(carrier, new Hub(globalHubTopStack.client, globalHubTopStack.scope.clone()));
const client = parent.getClient();
const scope = parent.getScope();
const isolationScope = parent.getIsolationScope();
setHubOnCarrier(carrier, new Hub(client, scope.clone(), isolationScope.clone()));
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
} from './exports';
export {
getCurrentHub,
getIsolationScope,
getHubFromCarrier,
Hub,
makeMain,
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/server-runtime-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ export class ServerRuntimeClient<
/**
* @inheritDoc
*/
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
protected _prepareEvent(
event: Event,
hint: EventHint,
scope?: Scope,
isolationScope?: Scope,
): PromiseLike<Event | null> {
if (this._options.platform) {
event.platform = event.platform || this._options.platform;
}
Expand All @@ -232,7 +237,7 @@ export class ServerRuntimeClient<
event.server_name = event.server_name || this._options.serverName;
}

return super._prepareEvent(event, hint, scope);
return super._prepareEvent(event, hint, scope, isolationScope);
}

/** Extract trace information from scope */
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/utils/prepareEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function prepareEvent(
hint: EventHint,
scope?: Scope,
client?: Client,
isolationScope?: Scope,
): PromiseLike<Event | null> {
const { normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = options;
const prepared: Event = {
Expand Down Expand Up @@ -80,6 +81,11 @@ export function prepareEvent(
// Merge scope data together
const data = getGlobalScope().getScopeData();

if (isolationScope) {
const isolationData = isolationScope.getScopeData();
mergeScopeData(data, isolationData);
}

if (finalScope) {
const finalScopeData = finalScope.getScopeData();
mergeScopeData(data, finalScopeData);
Expand Down
62 changes: 47 additions & 15 deletions packages/core/test/lib/prepareEvent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
ScopeContext,
} from '@sentry/types';
import { GLOBAL_OBJ, createStackParser } from '@sentry/utils';
import { setGlobalScope } from '../../src';
import { getCurrentHub, getIsolationScope, setGlobalScope } from '../../src';

import { Scope, getGlobalScope } from '../../src/scope';
import {
Expand Down Expand Up @@ -192,6 +192,7 @@ describe('parseEventHintOrCaptureContext', () => {
describe('prepareEvent', () => {
beforeEach(() => {
setGlobalScope(undefined);
getCurrentHub().getIsolationScope().clear();
});

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

const eventProcessor1 = jest.fn((a: unknown) => a) as EventProcessor;
const eventProcessor2 = jest.fn((b: unknown) => b) as EventProcessor;
const eventProcessor3 = jest.fn((b: unknown) => b) as EventProcessor;

const attachment1 = { filename: '1' } as Attachment;
const attachment2 = { filename: '2' } as Attachment;
const attachment3 = { filename: '3' } as Attachment;

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

const globalScope = getGlobalScope();
const isolationScope = getIsolationScope();

globalScope.addBreadcrumb(breadcrumb2);
globalScope.addEventProcessor(eventProcessor2);
globalScope.setSDKProcessingMetadata({ aa: 'aa' });
globalScope.addAttachment(attachment2);

const event = { message: 'foo', breadcrumbs: [breadcrumb3], fingerprint: ['dd'] };
isolationScope.addBreadcrumb(breadcrumb3);
isolationScope.addEventProcessor(eventProcessor3);
isolationScope.setSDKProcessingMetadata({ bb: 'bb' });
isolationScope.addAttachment(attachment3);

const event = { message: 'foo', breadcrumbs: [breadcrumb4], fingerprint: ['dd'] };

const options = {} as ClientOptions;
const processedEvent = await prepareEvent(
Expand All @@ -277,15 +287,18 @@ describe('prepareEvent', () => {
integrations: [],
},
scope,
undefined,
isolationScope,
);

expect(eventProcessor1).toHaveBeenCalledTimes(1);
expect(eventProcessor2).toHaveBeenCalledTimes(1);
expect(eventProcessor3).toHaveBeenCalledTimes(1);

// Test that attachments are correctly merged
expect(eventProcessor1).toHaveBeenCalledWith(processedEvent, {
integrations: [],
attachments: [attachment2, attachment1],
attachments: [attachment2, attachment3, attachment1],
});

expect(processedEvent).toEqual({
Expand All @@ -298,9 +311,10 @@ describe('prepareEvent', () => {
extra: { extra1: 'aa', extra2: 'aa' },
contexts: { os: { name: 'os1' }, culture: { display_name: 'name1' } },
fingerprint: ['dd', 'aa'],
breadcrumbs: [breadcrumb3, breadcrumb2, breadcrumb1],
breadcrumbs: [breadcrumb4, breadcrumb2, breadcrumb3, breadcrumb1],
sdkProcessingMetadata: {
aa: 'aa',
bb: 'bb',
propagationContext: {
spanId: '1',
traceId: '1',
Expand All @@ -312,33 +326,50 @@ describe('prepareEvent', () => {
it('works without a scope', async () => {
const breadcrumb1 = { message: '1', timestamp: 111 } as Breadcrumb;
const breadcrumb2 = { message: '2', timestamp: 222 } as Breadcrumb;
const breadcrumb3 = { message: '3', timestamp: 333 } as Breadcrumb;

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

const attachment1 = { filename: '1' } as Attachment;
const attachment2 = { filename: '2' } as Attachment;
const attachmentGlobal = { filename: 'global scope attachment' } as Attachment;
const attachmentIsolation = { filename: 'isolation scope attachment' } as Attachment;
const attachmentHint = { filename: 'hint attachment' } as Attachment;

const globalScope = getGlobalScope();
const isolationScope = getIsolationScope();

globalScope.addBreadcrumb(breadcrumb1);
globalScope.addEventProcessor(eventProcessor1);
globalScope.setSDKProcessingMetadata({ aa: 'aa' });
globalScope.addAttachment(attachment1);
globalScope.addAttachment(attachmentGlobal);

const event = { message: 'foo', breadcrumbs: [breadcrumb2], fingerprint: ['dd'] };
isolationScope.addBreadcrumb(breadcrumb2);
isolationScope.addEventProcessor(eventProcessor2);
isolationScope.setSDKProcessingMetadata({ bb: 'bb' });
isolationScope.addAttachment(attachmentIsolation);

const event = { message: 'foo', breadcrumbs: [breadcrumb3], fingerprint: ['dd'] };

const options = {} as ClientOptions;
const processedEvent = await prepareEvent(options, event, {
integrations: [],
attachments: [attachment2],
});
const processedEvent = await prepareEvent(
options,
event,
{
integrations: [],
attachments: [attachmentHint],
},
undefined,
undefined,
isolationScope,
);

expect(eventProcessor1).toHaveBeenCalledTimes(1);
expect(eventProcessor2).toHaveBeenCalledTimes(1);

// Test that attachments are correctly merged
expect(eventProcessor1).toHaveBeenCalledWith(processedEvent, {
integrations: [],
attachments: [attachment2, attachment1],
attachments: [attachmentHint, attachmentGlobal, attachmentIsolation],
});

expect(processedEvent).toEqual({
Expand All @@ -347,10 +378,11 @@ describe('prepareEvent', () => {
environment: 'production',
message: 'foo',
fingerprint: ['dd'],
breadcrumbs: [breadcrumb2, breadcrumb1],
breadcrumbs: [breadcrumb3, breadcrumb1, breadcrumb2],
sdkProcessingMetadata: {
aa: 'aa',
propagationContext: globalScope.getPropagationContext(),
bb: 'bb',
propagationContext: isolationScope.getPropagationContext(),
},
});
});
Expand Down
2 changes: 2 additions & 0 deletions packages/deno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export {
getCurrentHub,
getClient,
getCurrentScope,
getGlobalScope,
getIsolationScope,
Hub,
lastEventId,
makeMain,
Expand Down
2 changes: 2 additions & 0 deletions packages/feedback/src/util/prepareFeedbackEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Scope } from '@sentry/core';
import { getIsolationScope } from '@sentry/core';
import { prepareEvent } from '@sentry/core';
import type { Client, FeedbackEvent } from '@sentry/types';

Expand Down Expand Up @@ -26,6 +27,7 @@ export async function prepareFeedbackEvent({
eventHint,
scope,
client,
getIsolationScope(),
)) as FeedbackEvent | null;

if (preparedEvent === null) {
Expand Down
4 changes: 2 additions & 2 deletions packages/hub/test/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ describe('global', () => {
});

test('getGlobalHub', () => {
const newestHub = new Hub(undefined, undefined, 999999);
const newestHub = new Hub(undefined, undefined, undefined, 999999);
GLOBAL_OBJ.__SENTRY__.hub = newestHub;
expect(getCurrentHub()).toBe(newestHub);
});

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