Skip to content

Commit ed50b0c

Browse files
Merge branch 'develop' into develop
2 parents 16e142d + a05de17 commit ed50b0c

File tree

20 files changed

+235
-104
lines changed

20 files changed

+235
-104
lines changed

packages/browser/src/profiling/integration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EventProcessor, Hub, Integration, Transaction } from '@sentry/types';
1+
import type { EventEnvelope, EventProcessor, Hub, Integration, Transaction } from '@sentry/types';
22
import type { Profile } from '@sentry/types/src/profiling';
33
import { logger } from '@sentry/utils';
44

@@ -110,7 +110,7 @@ export class BrowserProfilingIntegration implements Integration {
110110
}
111111
}
112112

113-
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
113+
addProfilesToEnvelope(envelope as EventEnvelope, profilesToAddToEnvelope);
114114
});
115115
} else {
116116
logger.warn('[Profiling] Client does not support hooks, profiling will be disabled');

packages/browser/src/profiling/utils.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-lines */
22

33
import { DEFAULT_ENVIRONMENT, getClient, getCurrentHub } from '@sentry/core';
4-
import type { DebugImage, Envelope, Event, StackFrame, StackParser, Transaction } from '@sentry/types';
4+
import type { DebugImage, Envelope, Event, EventEnvelope, StackFrame, StackParser, Transaction } from '@sentry/types';
55
import type { Profile, ThreadCpuProfile } from '@sentry/types/src/profiling';
66
import { GLOBAL_OBJ, browserPerformanceTimeOrigin, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils';
77

@@ -300,13 +300,12 @@ export function convertJSSelfProfileToSampledFormat(input: JSSelfProfile): Profi
300300
* Adds items to envelope if they are not already present - mutates the envelope.
301301
* @param envelope
302302
*/
303-
export function addProfilesToEnvelope(envelope: Envelope, profiles: Profile[]): Envelope {
303+
export function addProfilesToEnvelope(envelope: EventEnvelope, profiles: Profile[]): Envelope {
304304
if (!profiles.length) {
305305
return envelope;
306306
}
307307

308308
for (const profile of profiles) {
309-
// @ts-expect-error untyped envelope
310309
envelope[1].push([{ type: 'profile' }, profile]);
311310
}
312311
return envelope;

packages/core/src/hub.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class Hub implements HubInterface {
142142
*/
143143
public pushScope(): Scope {
144144
// We want to clone the content of prev scope
145-
const scope = Scope.clone(this.getScope());
145+
const scope = this.getScope().clone();
146146
this.getStack().push({
147147
client: this.getClient(),
148148
scope,
@@ -578,7 +578,7 @@ export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub(
578578
// If there's no hub on current domain, or it's an old API, assign a new one
579579
if (!hasHubOnCarrier(carrier) || getHubFromCarrier(carrier).isOlderThan(API_VERSION)) {
580580
const globalHubTopStack = parent.getStackTop();
581-
setHubOnCarrier(carrier, new Hub(globalHubTopStack.client, Scope.clone(globalHubTopStack.scope)));
581+
setHubOnCarrier(carrier, new Hub(globalHubTopStack.client, globalHubTopStack.scope.clone()));
582582
}
583583
}
584584

packages/core/src/scope.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,27 +110,33 @@ export class Scope implements ScopeInterface {
110110

111111
/**
112112
* Inherit values from the parent scope.
113-
* @param scope to clone.
113+
* @deprecated Use `scope.clone()` and `new Scope()` instead.
114114
*/
115115
public static clone(scope?: Scope): Scope {
116+
return scope ? scope.clone() : new Scope();
117+
}
118+
119+
/**
120+
* Clone this scope instance.
121+
*/
122+
public clone(): Scope {
116123
const newScope = new Scope();
117-
if (scope) {
118-
newScope._breadcrumbs = [...scope._breadcrumbs];
119-
newScope._tags = { ...scope._tags };
120-
newScope._extra = { ...scope._extra };
121-
newScope._contexts = { ...scope._contexts };
122-
newScope._user = scope._user;
123-
newScope._level = scope._level;
124-
newScope._span = scope._span;
125-
newScope._session = scope._session;
126-
newScope._transactionName = scope._transactionName;
127-
newScope._fingerprint = scope._fingerprint;
128-
newScope._eventProcessors = [...scope._eventProcessors];
129-
newScope._requestSession = scope._requestSession;
130-
newScope._attachments = [...scope._attachments];
131-
newScope._sdkProcessingMetadata = { ...scope._sdkProcessingMetadata };
132-
newScope._propagationContext = { ...scope._propagationContext };
133-
}
124+
newScope._breadcrumbs = [...this._breadcrumbs];
125+
newScope._tags = { ...this._tags };
126+
newScope._extra = { ...this._extra };
127+
newScope._contexts = { ...this._contexts };
128+
newScope._user = this._user;
129+
newScope._level = this._level;
130+
newScope._span = this._span;
131+
newScope._session = this._session;
132+
newScope._transactionName = this._transactionName;
133+
newScope._fingerprint = this._fingerprint;
134+
newScope._eventProcessors = [...this._eventProcessors];
135+
newScope._requestSession = this._requestSession;
136+
newScope._attachments = [...this._attachments];
137+
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
138+
newScope._propagationContext = { ...this._propagationContext };
139+
134140
return newScope;
135141
}
136142

packages/core/src/utils/prepareEvent.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ export function prepareEvent(
7474

7575
// If we have scope given to us, use it as the base for further modifications.
7676
// This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
77-
let finalScope = scope;
78-
if (hint.captureContext) {
79-
finalScope = Scope.clone(finalScope).update(hint.captureContext);
80-
}
77+
const finalScope = getFinalScope(scope, hint.captureContext);
8178

8279
if (hint.mechanism) {
8380
addExceptionMechanism(prepared, hint.mechanism);
@@ -349,6 +346,16 @@ function normalizeEvent(event: Event | null, depth: number, maxBreadth: number):
349346
return normalized;
350347
}
351348

349+
function getFinalScope(scope: Scope | undefined, captureContext: CaptureContext | undefined): Scope | undefined {
350+
if (!captureContext) {
351+
return scope;
352+
}
353+
354+
const finalScope = scope ? scope.clone() : new Scope();
355+
finalScope.update(captureContext);
356+
return finalScope;
357+
}
358+
352359
/**
353360
* Parse either an `EventHint` directly, or convert a `CaptureContext` to an `EventHint`.
354361
* This is used to allow to update method signatures that used to accept a `CaptureContext` but should now accept an `EventHint`.

packages/opentelemetry-node/test/spanprocessor.test.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,7 @@ import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base';
55
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
66
import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
77
import type { SpanStatusType } from '@sentry/core';
8-
import {
9-
Hub,
10-
Scope,
11-
Span as SentrySpan,
12-
Transaction,
13-
addTracingExtensions,
14-
createTransport,
15-
makeMain,
16-
} from '@sentry/core';
8+
import { Hub, Span as SentrySpan, Transaction, addTracingExtensions, createTransport, makeMain } from '@sentry/core';
179
import { NodeClient } from '@sentry/node';
1810
import { resolvedSyncPromise } from '@sentry/utils';
1911

@@ -973,7 +965,7 @@ describe('SentrySpanProcessor', () => {
973965
hub = new Hub(client);
974966
makeMain(hub);
975967

976-
const newHub = new Hub(client, Scope.clone(hub.getScope()));
968+
const newHub = new Hub(client, hub.getScope().clone());
977969
newHub.configureScope(scope => {
978970
scope.setTag('foo', 'bar');
979971
});

packages/opentelemetry/src/contextManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Context, ContextManager } from '@opentelemetry/api';
22
import type { Carrier, Hub } from '@sentry/core';
3+
import { ensureHubOnCarrier } from '@sentry/core';
34

4-
import { ensureHubOnCarrier, getCurrentHub, getHubFromCarrier } from './custom/hub';
5+
import { getCurrentHub, getHubFromCarrier } from './custom/hub';
56
import { setHubOnContext } from './utils/contextData';
67

78
function createNewHub(parent: Hub | undefined): Hub {

packages/opentelemetry/src/custom/client.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { trace } from '@opentelemetry/api';
33
import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
44
import type { BaseClient, Scope } from '@sentry/core';
55
import { SDK_VERSION } from '@sentry/core';
6-
import type { Client, Event, EventHint } from '@sentry/types';
6+
import type { CaptureContext, Client, Event, EventHint } from '@sentry/types';
77

88
import type { OpenTelemetryClient as OpenTelemetryClientInterface } from '../types';
99
import { OpenTelemetryScope } from './scope';
@@ -65,14 +65,14 @@ export function wrapClientClass<
6565

6666
/**
6767
* Extends the base `_prepareEvent` so that we can properly handle `captureContext`.
68-
* This uses `Scope.clone()`, which we need to replace with `NodeExperimentalScope.clone()` for this client.
68+
* This uses `Scope.clone()`, which we need to replace with `OpenTelemetryScope.clone()` for this client.
6969
*/
7070
protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike<Event | null> {
7171
let actualScope = scope;
7272

7373
// Remove `captureContext` hint and instead clone already here
7474
if (hint && hint.captureContext) {
75-
actualScope = OpenTelemetryScope.clone(scope);
75+
actualScope = getFinalScope(scope, hint.captureContext);
7676
delete hint.captureContext;
7777
}
7878

@@ -83,3 +83,9 @@ export function wrapClientClass<
8383
return OpenTelemetryClient as unknown as WrappedClassConstructor;
8484
}
8585
/* eslint-enable @typescript-eslint/no-explicit-any */
86+
87+
function getFinalScope(scope: Scope | undefined, captureContext: CaptureContext): Scope | undefined {
88+
const finalScope = scope ? scope.clone() : new OpenTelemetryScope();
89+
finalScope.update(captureContext);
90+
return finalScope;
91+
}

packages/opentelemetry/src/custom/hub.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,6 @@ export class OpenTelemetryHub extends Hub {
1313
public constructor(client?: Client, scope: Scope = new OpenTelemetryScope()) {
1414
super(client, scope);
1515
}
16-
17-
/**
18-
* @inheritDoc
19-
*/
20-
public pushScope(): Scope {
21-
// We want to clone the content of prev scope
22-
const scope = OpenTelemetryScope.clone(this.getScope());
23-
this.getStack().push({
24-
client: this.getClient(),
25-
scope,
26-
});
27-
return scope;
28-
}
2916
}
3017

3118
/** Custom getClient method that uses the custom hub. */
@@ -110,10 +97,7 @@ export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub(
11097
// If there's no hub on current domain, or it's an old API, assign a new one
11198
if (!hasHubOnCarrier(carrier) || getHubFromCarrier(carrier).isOlderThan(API_VERSION)) {
11299
const globalHubTopStack = parent.getStackTop();
113-
setHubOnCarrier(
114-
carrier,
115-
new OpenTelemetryHub(globalHubTopStack.client, OpenTelemetryScope.clone(globalHubTopStack.scope)),
116-
);
100+
setHubOnCarrier(carrier, new OpenTelemetryHub(globalHubTopStack.client, globalHubTopStack.scope.clone()));
117101
}
118102
}
119103

packages/opentelemetry/src/custom/scope.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,30 @@ export class OpenTelemetryScope extends Scope {
2323
* @inheritDoc
2424
*/
2525
public static clone(scope?: Scope): Scope {
26+
return scope ? scope.clone() : new OpenTelemetryScope();
27+
}
28+
29+
/**
30+
* Clone this scope instance.
31+
*/
32+
public clone(): OpenTelemetryScope {
2633
const newScope = new OpenTelemetryScope();
27-
if (scope) {
28-
newScope._breadcrumbs = [...scope['_breadcrumbs']];
29-
newScope._tags = { ...scope['_tags'] };
30-
newScope._extra = { ...scope['_extra'] };
31-
newScope._contexts = { ...scope['_contexts'] };
32-
newScope._user = scope['_user'];
33-
newScope._level = scope['_level'];
34-
newScope._span = scope['_span'];
35-
newScope._session = scope['_session'];
36-
newScope._transactionName = scope['_transactionName'];
37-
newScope._fingerprint = scope['_fingerprint'];
38-
newScope._eventProcessors = [...scope['_eventProcessors']];
39-
newScope._requestSession = scope['_requestSession'];
40-
newScope._attachments = [...scope['_attachments']];
41-
newScope._sdkProcessingMetadata = { ...scope['_sdkProcessingMetadata'] };
42-
newScope._propagationContext = { ...scope['_propagationContext'] };
43-
}
34+
newScope._breadcrumbs = [...this['_breadcrumbs']];
35+
newScope._tags = { ...this['_tags'] };
36+
newScope._extra = { ...this['_extra'] };
37+
newScope._contexts = { ...this['_contexts'] };
38+
newScope._user = this['_user'];
39+
newScope._level = this['_level'];
40+
newScope._span = this['_span'];
41+
newScope._session = this['_session'];
42+
newScope._transactionName = this['_transactionName'];
43+
newScope._fingerprint = this['_fingerprint'];
44+
newScope._eventProcessors = [...this['_eventProcessors']];
45+
newScope._requestSession = this['_requestSession'];
46+
newScope._attachments = [...this['_attachments']];
47+
newScope._sdkProcessingMetadata = { ...this['_sdkProcessingMetadata'] };
48+
newScope._propagationContext = { ...this['_propagationContext'] };
49+
4450
return newScope;
4551
}
4652

packages/opentelemetry/src/spanExporter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ function maybeSend(spans: ReadableSpan[]): ReadableSpan[] {
112112

113113
// Now finish the transaction, which will send it together with all the spans
114114
// We make sure to use the current span as the activeSpan for this transaction
115-
const scope = getSpanScope(span);
116-
const forkedScope = OpenTelemetryScope.clone(scope as OpenTelemetryScope | undefined) as OpenTelemetryScope;
115+
const scope = getSpanScope(span) as OpenTelemetryScope | undefined;
116+
const forkedScope = scope ? scope.clone() : new OpenTelemetryScope();
117117
forkedScope.activeSpan = span as unknown as Span;
118118

119119
transaction.finishWithScope(convertOtelTimeToSeconds(span.endTime), forkedScope);

packages/opentelemetry/test/custom/scope.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ describe('NodeExperimentalScope', () => {
3030
scope['_attachments'] = [{ data: '123', filename: 'test.txt' }];
3131
scope['_sdkProcessingMetadata'] = { sdk: 'bar' };
3232

33+
// eslint-disable-next-line deprecation/deprecation
3334
const scope2 = OpenTelemetryScope.clone(scope);
3435

3536
expect(scope2).toBeInstanceOf(OpenTelemetryScope);
@@ -68,6 +69,7 @@ describe('NodeExperimentalScope', () => {
6869
});
6970

7071
it('clone() works without existing scope', () => {
72+
// eslint-disable-next-line deprecation/deprecation
7173
const scope = OpenTelemetryScope.clone(undefined);
7274

7375
expect(scope).toBeInstanceOf(OpenTelemetryScope);

packages/replay/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
"devDependencies": {
4949
"@babel/core": "^7.17.5",
5050
"@sentry-internal/replay-worker": "7.86.0",
51-
"@sentry-internal/rrweb": "2.2.0",
52-
"@sentry-internal/rrweb-snapshot": "2.2.0",
51+
"@sentry-internal/rrweb": "2.5.0",
52+
"@sentry-internal/rrweb-snapshot": "2.5.0",
5353
"fflate": "^0.8.1",
5454
"jsdom-worker": "^0.2.1"
5555
},
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { ErrorEvent, Event } from '@sentry/types';
2+
3+
import type { ReplayContainer } from '../types';
4+
import { createBreadcrumb } from '../util/createBreadcrumb';
5+
import { isErrorEvent } from '../util/eventUtils';
6+
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';
7+
8+
type BeforeSendEventCallback = (event: Event) => void;
9+
10+
/**
11+
* Returns a listener to be added to `client.on('afterSendErrorEvent, listener)`.
12+
*/
13+
export function handleBeforeSendEvent(replay: ReplayContainer): BeforeSendEventCallback {
14+
return (event: Event) => {
15+
if (!replay.isEnabled() || !isErrorEvent(event)) {
16+
return;
17+
}
18+
19+
handleHydrationError(replay, event);
20+
};
21+
}
22+
23+
function handleHydrationError(replay: ReplayContainer, event: ErrorEvent): void {
24+
const exceptionValue = event.exception && event.exception.values && event.exception.values[0].value;
25+
if (typeof exceptionValue !== 'string') {
26+
return;
27+
}
28+
29+
if (
30+
// Only matches errors in production builds of react-dom
31+
// Example https://reactjs.org/docs/error-decoder.html?invariant=423
32+
exceptionValue.match(/reactjs\.org\/docs\/error-decoder\.html\?invariant=(418|419|422|423|425)/) ||
33+
// Development builds of react-dom
34+
// Example Text: content did not match. Server: "A" Client: "B"
35+
exceptionValue.match(/(hydration|content does not match|did not match)/i)
36+
) {
37+
const breadcrumb = createBreadcrumb({
38+
category: 'replay.hydrate-error',
39+
});
40+
addBreadcrumbEvent(replay, breadcrumb);
41+
}
42+
}

packages/replay/src/replay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-lines */ // TODO: We might want to split this file up
22
import { EventType, record } from '@sentry-internal/rrweb';
33
import { captureException, getClient, getCurrentHub } from '@sentry/core';
4-
import type { ReplayRecordingMode, Transaction } from '@sentry/types';
4+
import type { Event as SentryEvent, ReplayRecordingMode, Transaction } from '@sentry/types';
55
import { logger } from '@sentry/utils';
66

77
import {

packages/replay/src/util/addGlobalListeners.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Client, DynamicSamplingContext } from '@sentry/types';
44
import { addClickKeypressInstrumentationHandler, addHistoryInstrumentationHandler } from '@sentry/utils';
55

66
import { handleAfterSendEvent } from '../coreHandlers/handleAfterSendEvent';
7+
import { handleBeforeSendEvent } from '../coreHandlers/handleBeforeSendEvent';
78
import { handleDomListener } from '../coreHandlers/handleDom';
89
import { handleGlobalEventListener } from '../coreHandlers/handleGlobalEvent';
910
import { handleHistorySpanListener } from '../coreHandlers/handleHistory';
@@ -35,6 +36,7 @@ export function addGlobalListeners(replay: ReplayContainer): void {
3536

3637
// If a custom client has no hooks yet, we continue to use the "old" implementation
3738
if (hasHooks(client)) {
39+
client.on('beforeSendEvent', handleBeforeSendEvent(replay));
3840
client.on('afterSendEvent', handleAfterSendEvent(replay));
3941
client.on('createDsc', (dsc: DynamicSamplingContext) => {
4042
const replayId = replay.getSessionId();

0 commit comments

Comments
 (0)