Skip to content

Commit afb900c

Browse files
mydeaLms24
andauthored
feat(core): Extract scope application to util (#9804)
This is a try to move the application of scope data to events out of the scope class into a util function. This changes the flow to be: 1. The scope has a `getScopeData()` method that returns data that should be applied to events. 2. The `prepareEvent` method uses that to actually apply scope data to the event etc. 3. This also makes it much easier to do the experimentation in node-experimental (#9799) because we only need to overwrite this, and can leave the rest of the event processing the same. I _think_ this makes sense but would appreciate some more eyes on this as well. For me the separation makes also more sense, as e.g. the application of event processors etc. should not really be tied to the scope at all. This is also is a first step to then allow us to add global scopes more easily. --------- Co-authored-by: Lukas Stracke <[email protected]>
1 parent d8eba90 commit afb900c

File tree

13 files changed

+220
-197
lines changed

13 files changed

+220
-197
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export {
6363
convertIntegrationFnToClass,
6464
} from './integration';
6565
export { FunctionToString, InboundFilters, LinkedErrors } from './integrations';
66+
export { applyScopeDataToEvent } from './utils/applyScopeDataToEvent';
6667
export { prepareEvent } from './utils/prepareEvent';
6768
export { createCheckInEnvelope } from './checkin';
6869
export { hasTracingEnabled } from './utils/hasTracingEnabled';

packages/core/src/scope.ts

Lines changed: 48 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
RequestSession,
1616
Scope as ScopeInterface,
1717
ScopeContext,
18+
ScopeData,
1819
Session,
1920
Severity,
2021
SeverityLevel,
@@ -26,6 +27,7 @@ import { arrayify, dateTimestampInSeconds, isPlainObject, uuid4 } from '@sentry/
2627

2728
import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors';
2829
import { updateSession } from './session';
30+
import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent';
2931

3032
/**
3133
* Default value for maximum number of breadcrumbs added to an event.
@@ -466,78 +468,65 @@ export class Scope implements ScopeInterface {
466468
return this;
467469
}
468470

471+
/** @inheritDoc */
472+
public getScopeData(): ScopeData {
473+
const {
474+
_breadcrumbs,
475+
_attachments,
476+
_contexts,
477+
_tags,
478+
_extra,
479+
_user,
480+
_level,
481+
_fingerprint,
482+
_eventProcessors,
483+
_propagationContext,
484+
_sdkProcessingMetadata,
485+
_transactionName,
486+
_span,
487+
} = this;
488+
489+
return {
490+
breadcrumbs: _breadcrumbs,
491+
attachments: _attachments,
492+
contexts: _contexts,
493+
tags: _tags,
494+
extra: _extra,
495+
user: _user,
496+
level: _level,
497+
fingerprint: _fingerprint || [],
498+
eventProcessors: _eventProcessors,
499+
propagationContext: _propagationContext,
500+
sdkProcessingMetadata: _sdkProcessingMetadata,
501+
transactionName: _transactionName,
502+
span: _span,
503+
};
504+
}
505+
469506
/**
470507
* Applies data from the scope to the event and runs all event processors on it.
471508
*
472509
* @param event Event
473510
* @param hint Object containing additional information about the original exception, for use by the event processors.
474511
* @hidden
512+
* @deprecated Use `applyScopeDataToEvent()` directly
475513
*/
476514
public applyToEvent(
477515
event: Event,
478516
hint: EventHint = {},
479-
additionalEventProcessors?: EventProcessor[],
517+
additionalEventProcessors: EventProcessor[] = [],
480518
): PromiseLike<Event | null> {
481-
if (this._extra && Object.keys(this._extra).length) {
482-
event.extra = { ...this._extra, ...event.extra };
483-
}
484-
if (this._tags && Object.keys(this._tags).length) {
485-
event.tags = { ...this._tags, ...event.tags };
486-
}
487-
if (this._user && Object.keys(this._user).length) {
488-
event.user = { ...this._user, ...event.user };
489-
}
490-
if (this._contexts && Object.keys(this._contexts).length) {
491-
event.contexts = { ...this._contexts, ...event.contexts };
492-
}
493-
if (this._level) {
494-
event.level = this._level;
495-
}
496-
if (this._transactionName) {
497-
event.transaction = this._transactionName;
498-
}
499-
500-
// We want to set the trace context for normal events only if there isn't already
501-
// a trace context on the event. There is a product feature in place where we link
502-
// errors with transaction and it relies on that.
503-
if (this._span) {
504-
event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
505-
const transaction = this._span.transaction;
506-
if (transaction) {
507-
event.sdkProcessingMetadata = {
508-
dynamicSamplingContext: transaction.getDynamicSamplingContext(),
509-
...event.sdkProcessingMetadata,
510-
};
511-
const transactionName = transaction.name;
512-
if (transactionName) {
513-
event.tags = { transaction: transactionName, ...event.tags };
514-
}
515-
}
516-
}
517-
518-
this._applyFingerprint(event);
519-
520-
const scopeBreadcrumbs = this._getBreadcrumbs();
521-
const breadcrumbs = [...(event.breadcrumbs || []), ...scopeBreadcrumbs];
522-
event.breadcrumbs = breadcrumbs.length > 0 ? breadcrumbs : undefined;
523-
524-
event.sdkProcessingMetadata = {
525-
...event.sdkProcessingMetadata,
526-
...this._sdkProcessingMetadata,
527-
propagationContext: this._propagationContext,
528-
};
519+
applyScopeDataToEvent(event, this.getScopeData());
529520

530521
// TODO (v8): Update this order to be: Global > Client > Scope
531-
return notifyEventProcessors(
532-
[
533-
...(additionalEventProcessors || []),
534-
// eslint-disable-next-line deprecation/deprecation
535-
...getGlobalEventProcessors(),
536-
...this._eventProcessors,
537-
],
538-
event,
539-
hint,
540-
);
522+
const eventProcessors: EventProcessor[] = [
523+
...additionalEventProcessors,
524+
// eslint-disable-next-line deprecation/deprecation
525+
...getGlobalEventProcessors(),
526+
...this._eventProcessors,
527+
];
528+
529+
return notifyEventProcessors(eventProcessors, event, hint);
541530
}
542531

543532
/**
@@ -564,13 +553,6 @@ export class Scope implements ScopeInterface {
564553
return this._propagationContext;
565554
}
566555

567-
/**
568-
* Get the breadcrumbs for this scope.
569-
*/
570-
protected _getBreadcrumbs(): Breadcrumb[] {
571-
return this._breadcrumbs;
572-
}
573-
574556
/**
575557
* This will be called on every set call.
576558
*/
@@ -586,25 +568,6 @@ export class Scope implements ScopeInterface {
586568
this._notifyingListeners = false;
587569
}
588570
}
589-
590-
/**
591-
* Applies fingerprint from the scope to the event if there's one,
592-
* uses message if there's one instead or get rid of empty fingerprint
593-
*/
594-
private _applyFingerprint(event: Event): void {
595-
// Make sure it's an array first and we actually have something in place
596-
event.fingerprint = event.fingerprint ? arrayify(event.fingerprint) : [];
597-
598-
// If we have something on the scope, then merge it with event
599-
if (this._fingerprint) {
600-
event.fingerprint = event.fingerprint.concat(this._fingerprint);
601-
}
602-
603-
// If we have no data at all, remove empty array default
604-
if (event.fingerprint && !event.fingerprint.length) {
605-
delete event.fingerprint;
606-
}
607-
}
608571
}
609572

610573
function generatePropagationContext(): PropagationContext {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { Breadcrumb, Event, PropagationContext, ScopeData, Span } from '@sentry/types';
2+
import { arrayify } from '@sentry/utils';
3+
4+
/**
5+
* Applies data from the scope to the event and runs all event processors on it.
6+
*/
7+
export function applyScopeDataToEvent(event: Event, data: ScopeData): void {
8+
const { fingerprint, span, breadcrumbs, sdkProcessingMetadata, propagationContext } = data;
9+
10+
// Apply general data
11+
applyDataToEvent(event, data);
12+
13+
// We want to set the trace context for normal events only if there isn't already
14+
// a trace context on the event. There is a product feature in place where we link
15+
// errors with transaction and it relies on that.
16+
if (span) {
17+
applySpanToEvent(event, span);
18+
}
19+
20+
applyFingerprintToEvent(event, fingerprint);
21+
applyBreadcrumbsToEvent(event, breadcrumbs);
22+
applySdkMetadataToEvent(event, sdkProcessingMetadata, propagationContext);
23+
}
24+
25+
function applyDataToEvent(event: Event, data: ScopeData): void {
26+
const { extra, tags, user, contexts, level, transactionName } = data;
27+
28+
if (extra && Object.keys(extra).length) {
29+
event.extra = { ...extra, ...event.extra };
30+
}
31+
if (tags && Object.keys(tags).length) {
32+
event.tags = { ...tags, ...event.tags };
33+
}
34+
if (user && Object.keys(user).length) {
35+
event.user = { ...user, ...event.user };
36+
}
37+
if (contexts && Object.keys(contexts).length) {
38+
event.contexts = { ...contexts, ...event.contexts };
39+
}
40+
if (level) {
41+
event.level = level;
42+
}
43+
if (transactionName) {
44+
event.transaction = transactionName;
45+
}
46+
}
47+
48+
function applyBreadcrumbsToEvent(event: Event, breadcrumbs: Breadcrumb[]): void {
49+
const mergedBreadcrumbs = [...(event.breadcrumbs || []), ...breadcrumbs];
50+
event.breadcrumbs = mergedBreadcrumbs.length ? mergedBreadcrumbs : undefined;
51+
}
52+
53+
function applySdkMetadataToEvent(
54+
event: Event,
55+
sdkProcessingMetadata: ScopeData['sdkProcessingMetadata'],
56+
propagationContext: PropagationContext,
57+
): void {
58+
event.sdkProcessingMetadata = {
59+
...event.sdkProcessingMetadata,
60+
...sdkProcessingMetadata,
61+
propagationContext: propagationContext,
62+
};
63+
}
64+
65+
function applySpanToEvent(event: Event, span: Span): void {
66+
event.contexts = { trace: span.getTraceContext(), ...event.contexts };
67+
const transaction = span.transaction;
68+
if (transaction) {
69+
event.sdkProcessingMetadata = {
70+
dynamicSamplingContext: transaction.getDynamicSamplingContext(),
71+
...event.sdkProcessingMetadata,
72+
};
73+
const transactionName = transaction.name;
74+
if (transactionName) {
75+
event.tags = { transaction: transactionName, ...event.tags };
76+
}
77+
}
78+
}
79+
80+
/**
81+
* Applies fingerprint from the scope to the event if there's one,
82+
* uses message if there's one instead or get rid of empty fingerprint
83+
*/
84+
function applyFingerprintToEvent(event: Event, fingerprint: ScopeData['fingerprint'] | undefined): void {
85+
// Make sure it's an array first and we actually have something in place
86+
event.fingerprint = event.fingerprint ? arrayify(event.fingerprint) : [];
87+
88+
// If we have something on the scope, then merge it with event
89+
if (fingerprint) {
90+
event.fingerprint = event.fingerprint.concat(fingerprint);
91+
}
92+
93+
// If we have no data at all, remove empty array default
94+
if (event.fingerprint && !event.fingerprint.length) {
95+
delete event.fingerprint;
96+
}
97+
}

packages/core/src/utils/prepareEvent.ts

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,12 @@ import type {
99
StackFrame,
1010
StackParser,
1111
} from '@sentry/types';
12-
import {
13-
GLOBAL_OBJ,
14-
addExceptionMechanism,
15-
dateTimestampInSeconds,
16-
normalize,
17-
resolvedSyncPromise,
18-
truncate,
19-
uuid4,
20-
} from '@sentry/utils';
12+
import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils';
2113

2214
import { DEFAULT_ENVIRONMENT } from '../constants';
2315
import { getGlobalEventProcessors, notifyEventProcessors } from '../eventProcessors';
2416
import { Scope } from '../scope';
17+
import { applyScopeDataToEvent } from './applyScopeDataToEvent';
2518

2619
/**
2720
* This type makes sure that we get either a CaptureContext, OR an EventHint.
@@ -80,10 +73,13 @@ export function prepareEvent(
8073
addExceptionMechanism(prepared, hint.mechanism);
8174
}
8275

83-
// We prepare the result here with a resolved Event.
84-
let result = resolvedSyncPromise<Event | null>(prepared);
85-
8676
const clientEventProcessors = client && client.getEventProcessors ? client.getEventProcessors() : [];
77+
// TODO (v8): Update this order to be: Global > Client > Scope
78+
const eventProcessors = [
79+
...clientEventProcessors,
80+
// eslint-disable-next-line deprecation/deprecation
81+
...getGlobalEventProcessors(),
82+
];
8783

8884
// This should be the last thing called, since we want that
8985
// {@link Hub.addEventProcessor} gets the finished prepared event.
@@ -102,22 +98,15 @@ export function prepareEvent(
10298
}
10399
}
104100

105-
// In case we have a hub we reassign it.
106-
result = finalScope.applyToEvent(prepared, hint, clientEventProcessors);
107-
} else {
108-
// Apply client & global event processors even if there is no scope
109-
// TODO (v8): Update the order to be Global > Client
110-
result = notifyEventProcessors(
111-
[
112-
...clientEventProcessors,
113-
// eslint-disable-next-line deprecation/deprecation
114-
...getGlobalEventProcessors(),
115-
],
116-
prepared,
117-
hint,
118-
);
101+
const scopeData = finalScope.getScopeData();
102+
applyScopeDataToEvent(prepared, scopeData);
103+
104+
// Run scope event processors _after_ all other processors
105+
eventProcessors.push(...scopeData.eventProcessors);
119106
}
120107

108+
const result = notifyEventProcessors(eventProcessors, prepared, hint);
109+
121110
return result.then(evt => {
122111
if (evt) {
123112
// We apply the debug_meta field only after all event processors have ran, so that if any event processors modified

packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('Unit | util | prepareFeedbackEvent', () => {
1616
hub.bindClient(client);
1717

1818
client = hub.getClient()!;
19-
scope = hub.getScope()!;
19+
scope = hub.getScope();
2020
});
2121

2222
afterEach(() => {

0 commit comments

Comments
 (0)