Skip to content
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

feat(core): Add getGlobalScope() method #9920

Merged
merged 4 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/index.ts
Expand Up @@ -42,7 +42,7 @@ export {
} from './hub';
export { makeSession, closeSession, updateSession } from './session';
export { SessionFlusher } from './sessionflusher';
export { Scope } from './scope';
export { Scope, getGlobalScope, setGlobalScope } from './scope';
export {
notifyEventProcessors,
// eslint-disable-next-line deprecation/deprecation
Expand All @@ -63,7 +63,7 @@ export {
convertIntegrationFnToClass,
} from './integration';
export { FunctionToString, InboundFilters, LinkedErrors } from './integrations';
export { applyScopeDataToEvent } from './utils/applyScopeDataToEvent';
export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent';
export { prepareEvent } from './utils/prepareEvent';
export { createCheckInEnvelope } from './checkin';
export { hasTracingEnabled } from './utils/hasTracingEnabled';
Expand Down
34 changes: 32 additions & 2 deletions packages/core/src/scope.ts
Expand Up @@ -23,7 +23,7 @@ import type {
Transaction,
User,
} from '@sentry/types';
import { arrayify, dateTimestampInSeconds, isPlainObject, uuid4 } from '@sentry/utils';
import { dateTimestampInSeconds, isPlainObject, uuid4 } from '@sentry/utils';

import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors';
import { updateSession } from './session';
Expand All @@ -34,6 +34,12 @@ import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent';
*/
const DEFAULT_MAX_BREADCRUMBS = 100;

/**
* The global scope is kept in this module.
* When accessing this via `getGlobalScope()` we'll make sure to set one if none is currently present.
*/
let globalScope: ScopeInterface | undefined;

/**
* Holds additional event information. {@link Scope.applyToEvent} will be
* called by the client before an event will be sent.
Expand Down Expand Up @@ -455,9 +461,12 @@ export class Scope implements ScopeInterface {

/**
* @inheritDoc
* @deprecated Use `getScopeData()` instead.
*/
public getAttachments(): Attachment[] {
return this._attachments;
const data = this.getScopeData();

return data.attachments;
}

/**
Expand Down Expand Up @@ -570,6 +579,27 @@ export class Scope implements ScopeInterface {
}
}

/**
* Get the global scope.
* This scope is applied to _all_ events.
*/
export function getGlobalScope(): ScopeInterface {
if (!globalScope) {
globalScope = new Scope();
}

return globalScope;
}

/**
* This is mainly needed for tests.
* DO NOT USE this, as this is an internal API and subject to change.
* @hidden
*/
export function setGlobalScope(scope: ScopeInterface | undefined): void {
globalScope = scope;
}

function generatePropagationContext(): PropagationContext {
return {
traceId: uuid4(),
Expand Down
98 changes: 98 additions & 0 deletions packages/core/src/utils/applyScopeDataToEvent.ts
Expand Up @@ -22,6 +22,104 @@ export function applyScopeDataToEvent(event: Event, data: ScopeData): void {
applySdkMetadataToEvent(event, sdkProcessingMetadata, propagationContext);
}

/** Merge data of two scopes together. */
export function mergeScopeData(data: ScopeData, mergeData: ScopeData): void {
const {
extra,
tags,
user,
contexts,
level,
sdkProcessingMetadata,
breadcrumbs,
fingerprint,
eventProcessors,
attachments,
propagationContext,
transactionName,
span,
} = mergeData;

mergePropOverwrite(data, 'extra', extra);
mergePropOverwrite(data, 'tags', tags);
mergePropOverwrite(data, 'user', user);
mergePropOverwrite(data, 'contexts', contexts);
mergePropOverwrite(data, 'sdkProcessingMetadata', sdkProcessingMetadata);

if (level) {
data.level = level;
}

if (transactionName) {
data.transactionName = transactionName;
}

if (span) {
data.span = span;
}

if (breadcrumbs.length) {
data.breadcrumbs = [...data.breadcrumbs, ...breadcrumbs];
}

if (fingerprint.length) {
data.fingerprint = [...data.fingerprint, ...fingerprint];
}

if (eventProcessors.length) {
data.eventProcessors = [...data.eventProcessors, ...eventProcessors];
}

if (attachments.length) {
data.attachments = [...data.attachments, ...attachments];
}

data.propagationContext = { ...data.propagationContext, ...propagationContext };
}

/**
* Merge properties, overwriting existing keys.
* Exported only for tests.
*/
export function mergePropOverwrite<
Prop extends 'extra' | 'tags' | 'user' | 'contexts' | 'sdkProcessingMetadata',
Data extends ScopeData | Event,
>(data: Data, prop: Prop, mergeVal: Data[Prop]): void {
if (mergeVal && Object.keys(mergeVal).length) {
data[prop] = { ...data[prop], ...mergeVal };
}
}

/**
* Merge properties, keeping existing keys.
* Exported only for tests.
*/
export function mergePropKeep<
Prop extends 'extra' | 'tags' | 'user' | 'contexts' | 'sdkProcessingMetadata',
Data extends ScopeData | Event,
>(data: Data, prop: Prop, mergeVal: Data[Prop]): void {
if (mergeVal && Object.keys(mergeVal).length) {
data[prop] = { ...mergeVal, ...data[prop] };
}
}

/** Exported only for tests */
export function mergeArray<Prop extends 'breadcrumbs' | 'fingerprint'>(
event: Event,
prop: Prop,
mergeVal: ScopeData[Prop],
): void {
const prevVal = event[prop];
// If we are not merging any new values,
// we only need to proceed if there was an empty array before (as we want to replace it with undefined)
if (!mergeVal.length && (!prevVal || prevVal.length)) {
return;
}

const merged = [...(prevVal || []), ...mergeVal] as ScopeData[Prop];
event[prop] = merged.length ? merged : undefined;
}

function applyDataToEvent(event: Event, data: ScopeData): void {
const { extra, tags, user, contexts, level, transactionName } = data;

Expand Down
44 changes: 20 additions & 24 deletions packages/core/src/utils/prepareEvent.ts
Expand Up @@ -13,8 +13,8 @@ import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, t

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getGlobalEventProcessors, notifyEventProcessors } from '../eventProcessors';
import { Scope } from '../scope';
import { applyScopeDataToEvent } from './applyScopeDataToEvent';
import { Scope, getGlobalScope } from '../scope';
import { applyScopeDataToEvent, mergeScopeData } from './applyScopeDataToEvent';

/**
* This type makes sure that we get either a CaptureContext, OR an EventHint.
Expand Down Expand Up @@ -74,36 +74,32 @@ export function prepareEvent(
}

const clientEventProcessors = client && client.getEventProcessors ? client.getEventProcessors() : [];
// TODO (v8): Update this order to be: Global > Client > Scope
const eventProcessors = [
...clientEventProcessors,
// eslint-disable-next-line deprecation/deprecation
...getGlobalEventProcessors(),
];

// This should be the last thing called, since we want that
// {@link Hub.addEventProcessor} gets the finished prepared event.
//
// We need to check for the existence of `finalScope.getAttachments`
// because `getAttachments` can be undefined if users are using an older version
// of `@sentry/core` that does not have the `getAttachments` method.
// See: https://github.com/getsentry/sentry-javascript/issues/5229
// Merge scope data together
const data = getGlobalScope().getScopeData();
Copy link
Member Author

Choose a reason for hiding this comment

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

@AbhiPrasad I refactored this so that the global scope is merged/applied in prepareEvent.


if (finalScope) {
// Collect attachments from the hint and scope
if (finalScope.getAttachments) {
const attachments = [...(hint.attachments || []), ...finalScope.getAttachments()];
const finalScopeData = finalScope.getScopeData();
mergeScopeData(data, finalScopeData);
}

if (attachments.length) {
hint.attachments = attachments;
}
}
const attachments = [...(hint.attachments || []), ...data.attachments];
if (attachments.length) {
hint.attachments = attachments;
}

const scopeData = finalScope.getScopeData();
applyScopeDataToEvent(prepared, scopeData);
applyScopeDataToEvent(prepared, data);

// TODO (v8): Update this order to be: Global > Client > Scope
const eventProcessors = [
...clientEventProcessors,
// eslint-disable-next-line deprecation/deprecation
...getGlobalEventProcessors(),
// Run scope event processors _after_ all other processors
eventProcessors.push(...scopeData.eventProcessors);
}
...data.eventProcessors,
];

const result = notifyEventProcessors(eventProcessors, prepared, hint);

Expand Down
12 changes: 8 additions & 4 deletions packages/core/test/lib/base.test.ts
@@ -1,7 +1,7 @@
import type { Client, Envelope, Event, Span, Transaction } from '@sentry/types';
import { SentryError, SyncPromise, dsnToString, logger } from '@sentry/utils';

import { Hub, Scope, makeSession } from '../../src';
import { Hub, Scope, makeSession, setGlobalScope } from '../../src';
import * as integrationModule from '../../src/integration';
import { TestClient, getDefaultTestClientOptions } from '../mocks/client';
import { AdHocIntegration, TestIntegration } from '../mocks/integration';
Expand Down Expand Up @@ -54,6 +54,7 @@ describe('BaseClient', () => {
beforeEach(() => {
TestClient.sendEventCalled = undefined;
TestClient.instance = undefined;
setGlobalScope(undefined);
});

afterEach(() => {
Expand Down Expand Up @@ -756,7 +757,8 @@ describe('BaseClient', () => {
expect(TestClient.instance!.event!).toEqual(
expect.objectContaining({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
// also has trace context from global scope
contexts: { ...normalizedObject, trace: expect.anything() },
environment: 'production',
event_id: '42',
extra: normalizedObject,
Expand Down Expand Up @@ -805,7 +807,8 @@ describe('BaseClient', () => {
expect(TestClient.instance!.event!).toEqual(
expect.objectContaining({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
// also has trace context from global scope
contexts: { ...normalizedObject, trace: expect.anything() },
environment: 'production',
event_id: '42',
extra: normalizedObject,
Expand Down Expand Up @@ -859,7 +862,8 @@ describe('BaseClient', () => {
expect(TestClient.instance!.event!).toEqual(
expect.objectContaining({
breadcrumbs: [normalizedBreadcrumb, normalizedBreadcrumb, normalizedBreadcrumb],
contexts: normalizedObject,
// also has trace context from global scope
contexts: { ...normalizedObject, trace: expect.anything() },
environment: 'production',
event_id: '42',
extra: normalizedObject,
Expand Down