diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e18ea294f182..c4884edf939b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -61,7 +61,7 @@ export { _INTERNAL_shouldSkipAiProviderWrapping, _INTERNAL_clearAiProviderSkips, } from './utils/ai/providerSkip'; -export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent'; +export { applyScopeDataToEvent, mergeScopeData, getCombinedScopeData } from './utils/scopeData'; export { prepareEvent } from './utils/prepareEvent'; export type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; export { createCheckInEnvelope } from './checkin'; diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index a39aa75d7074..3408b01a5f96 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -1,14 +1,13 @@ import { serializeAttributes } from '../attributes'; import { getGlobalSingleton } from '../carrier'; import type { Client } from '../client'; -import { getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '../currentScopes'; +import { getClient, getCurrentScope, getIsolationScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import type { Scope, ScopeData } from '../scope'; import type { Integration } from '../types-hoist/integration'; import type { Log, SerializedLog } from '../types-hoist/log'; -import { mergeScopeData } from '../utils/applyScopeDataToEvent'; import { consoleSandbox, debug } from '../utils/debug-logger'; import { isParameterizedString } from '../utils/is'; +import { getCombinedScopeData } from '../utils/scopeData'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; @@ -98,7 +97,7 @@ export function _INTERNAL_captureLog( const { user: { id, email, username }, attributes: scopeAttributes = {}, - } = getMergedScopeData(currentScope); + } = getCombinedScopeData(getIsolationScope(), currentScope); setLogAttribute(processedLogAttributes, 'user.id', id, false); setLogAttribute(processedLogAttributes, 'user.email', email, false); @@ -212,20 +211,6 @@ export function _INTERNAL_getLogBuffer(client: Client): Array | u return _getBufferMap().get(client); } -/** - * Get the scope data for the current scope after merging with the - * global scope and isolation scope. - * - * @param currentScope - The current scope. - * @returns The scope data. - */ -function getMergedScopeData(currentScope: Scope): ScopeData { - const scopeData = getGlobalScope().getScopeData(); - mergeScopeData(scopeData, getIsolationScope().getScopeData()); - mergeScopeData(scopeData, currentScope.getScopeData()); - return scopeData; -} - function _getBufferMap(): WeakMap> { // The reference to the Client <> LogBuffer map is stored on the carrier to ensure it's always the same return getGlobalSingleton('clientToLogBufferMap', () => new WeakMap>()); diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index 7ac1372d1285..db98c476fff7 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -1,12 +1,12 @@ import { getGlobalSingleton } from '../carrier'; import type { Client } from '../client'; -import { getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '../currentScopes'; +import { getClient, getCurrentScope, getIsolationScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import type { Scope, ScopeData } from '../scope'; +import type { Scope } from '../scope'; import type { Integration } from '../types-hoist/integration'; import type { Metric, SerializedMetric, SerializedMetricAttributeValue } from '../types-hoist/metric'; -import { mergeScopeData } from '../utils/applyScopeDataToEvent'; import { debug } from '../utils/debug-logger'; +import { getCombinedScopeData } from '../utils/scopeData'; import { _getSpanForScope } from '../utils/spanOnScope'; import { timestampInSeconds } from '../utils/time'; import { _getTraceInfoFromScope } from '../utils/trace-info'; @@ -130,7 +130,7 @@ function _enrichMetricAttributes(beforeMetric: Metric, client: Client, currentSc // Add user attributes const { user: { id, email, username }, - } = getMergedScopeData(currentScope); + } = getCombinedScopeData(getIsolationScope(), currentScope); setMetricAttribute(processedMetricAttributes, 'user.id', id, false); setMetricAttribute(processedMetricAttributes, 'user.email', email, false); setMetricAttribute(processedMetricAttributes, 'user.name', username, false); @@ -288,20 +288,6 @@ export function _INTERNAL_getMetricBuffer(client: Client): Array> { // The reference to the Client <> MetricBuffer map is stored on the carrier to ensure it's always the same return getGlobalSingleton('clientToMetricBufferMap', () => new WeakMap>()); diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index fd1cb62440f4..3a127d332686 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -1,16 +1,15 @@ import type { Client } from '../client'; import { DEFAULT_ENVIRONMENT } from '../constants'; -import { getGlobalScope } from '../currentScopes'; import { notifyEventProcessors } from '../eventProcessors'; import type { CaptureContext, ScopeContext } from '../scope'; import { Scope } from '../scope'; import type { Event, EventHint } from '../types-hoist/event'; import type { ClientOptions } from '../types-hoist/options'; import type { StackParser } from '../types-hoist/stacktrace'; -import { applyScopeDataToEvent, mergeScopeData } from './applyScopeDataToEvent'; import { getFilenameToDebugIdMap } from './debug-ids'; import { addExceptionMechanism, uuid4 } from './misc'; import { normalize } from './normalize'; +import { applyScopeDataToEvent, getCombinedScopeData } from './scopeData'; import { truncate } from './string'; import { dateTimestampInSeconds } from './time'; @@ -79,17 +78,7 @@ export function prepareEvent( // This should be the last thing called, since we want that // {@link Scope.addEventProcessor} gets the finished prepared event. // 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); - } + const data = getCombinedScopeData(isolationScope, finalScope); const attachments = [...(hint.attachments || []), ...data.attachments]; if (attachments.length) { diff --git a/packages/core/src/utils/applyScopeDataToEvent.ts b/packages/core/src/utils/scopeData.ts similarity index 90% rename from packages/core/src/utils/applyScopeDataToEvent.ts rename to packages/core/src/utils/scopeData.ts index 3770c41977dc..6d8f68c747b5 100644 --- a/packages/core/src/utils/applyScopeDataToEvent.ts +++ b/packages/core/src/utils/scopeData.ts @@ -1,4 +1,5 @@ -import type { ScopeData } from '../scope'; +import { getGlobalScope } from '../currentScopes'; +import type { Scope, ScopeData } from '../scope'; import { getDynamicSamplingContextFromSpan } from '../tracing/dynamicSamplingContext'; import type { Breadcrumb } from '../types-hoist/breadcrumb'; import type { Event } from '../types-hoist/event'; @@ -113,6 +114,20 @@ export function mergeArray( event[prop] = merged.length ? merged : undefined; } +/** + * Get the scope data for the current scope after merging with the + * global scope and isolation scope. + * + * @param currentScope - The current scope. + * @returns The scope data. + */ +export function getCombinedScopeData(isolationScope: Scope | undefined, currentScope: Scope | undefined): ScopeData { + const scopeData = getGlobalScope().getScopeData(); + isolationScope && mergeScopeData(scopeData, isolationScope.getScopeData()); + currentScope && mergeScopeData(scopeData, currentScope.getScopeData()); + return scopeData; +} + function applyDataToEvent(event: Event, data: ScopeData): void { const { extra, tags, user, contexts, level, transactionName } = data; diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 339a57828e5b..f1e5c58550be 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -10,7 +10,7 @@ import { import { Scope } from '../../src/scope'; import type { Breadcrumb } from '../../src/types-hoist/breadcrumb'; import type { Event } from '../../src/types-hoist/event'; -import { applyScopeDataToEvent } from '../../src/utils/applyScopeDataToEvent'; +import { applyScopeDataToEvent } from '../../src/utils/scopeData'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; import { clearGlobalScope } from '../testutils'; diff --git a/packages/core/test/lib/utils/applyScopeDataToEvent.test.ts b/packages/core/test/lib/utils/scopeData.test.ts similarity index 87% rename from packages/core/test/lib/utils/applyScopeDataToEvent.test.ts rename to packages/core/test/lib/utils/scopeData.test.ts index a23404eaf70f..50af1179a4c4 100644 --- a/packages/core/test/lib/utils/applyScopeDataToEvent.test.ts +++ b/packages/core/test/lib/utils/scopeData.test.ts @@ -1,16 +1,18 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import type { ScopeData } from '../../../src'; -import { startInactiveSpan } from '../../../src'; +import { Scope, startInactiveSpan } from '../../../src'; +import * as currentScopes from '../../../src/currentScopes'; import type { Attachment } from '../../../src/types-hoist/attachment'; import type { Breadcrumb } from '../../../src/types-hoist/breadcrumb'; import type { Event, EventType } from '../../../src/types-hoist/event'; import type { EventProcessor } from '../../../src/types-hoist/eventprocessor'; import { applyScopeDataToEvent, + getCombinedScopeData, mergeAndOverwriteScopeData, mergeArray, mergeScopeData, -} from '../../../src/utils/applyScopeDataToEvent'; +} from '../../../src/utils/scopeData'; describe('mergeArray', () => { it.each([ @@ -353,3 +355,50 @@ describe('applyScopeDataToEvent', () => { }, ); }); + +describe('getCombinedScopeData', () => { + const globalScope = new Scope(); + const isolationScope = new Scope(); + const currentScope = new Scope(); + + it('returns the combined scope data with correct precedence', () => { + globalScope.setTag('foo', 'bar'); + globalScope.setTag('dogs', 'boring'); + globalScope.setTag('global', 'global'); + + isolationScope.setTag('dogs', 'great'); + isolationScope.setTag('foo', 'nope'); + isolationScope.setTag('isolation', 'isolation'); + + currentScope.setTag('foo', 'baz'); + currentScope.setTag('current', 'current'); + + vi.spyOn(currentScopes, 'getGlobalScope').mockReturnValue(globalScope); + + expect(getCombinedScopeData(isolationScope, currentScope)).toEqual({ + attachments: [], + attributes: {}, + breadcrumbs: [], + contexts: {}, + eventProcessors: [], + extra: {}, + fingerprint: [], + level: undefined, + propagationContext: { + sampleRand: expect.any(Number), + traceId: expect.any(String), + }, + sdkProcessingMetadata: {}, + span: undefined, + tags: { + current: 'current', + global: 'global', + isolation: 'isolation', + foo: 'baz', + dogs: 'great', + }, + transactionName: undefined, + user: {}, + }); + }); +}); diff --git a/packages/node-core/src/integrations/anr/index.ts b/packages/node-core/src/integrations/anr/index.ts index e33c92a1eb3b..e2207f9379c7 100644 --- a/packages/node-core/src/integrations/anr/index.ts +++ b/packages/node-core/src/integrations/anr/index.ts @@ -5,12 +5,11 @@ import { debug, defineIntegration, getClient, + getCombinedScopeData, getCurrentScope, getFilenameToDebugIdMap, - getGlobalScope, getIsolationScope, GLOBAL_OBJ, - mergeScopeData, } from '@sentry/core'; import { NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; @@ -35,9 +34,7 @@ function globalWithScopeFetchFn(): typeof GLOBAL_OBJ & { __SENTRY_GET_SCOPES__?: /** Fetches merged scope data */ function getScopeData(): ScopeData { - const scope = getGlobalScope().getScopeData(); - mergeScopeData(scope, getIsolationScope().getScopeData()); - mergeScopeData(scope, getCurrentScope().getScopeData()); + const scope = getCombinedScopeData(getIsolationScope(), getCurrentScope()); // We remove attachments because they likely won't serialize well as json scope.attachments = [];