Skip to content

Commit 95f721f

Browse files
authored
fix(utils): Make xhr instrumentation independent of parallel running SDK versions (#7836)
1 parent 7b50bfa commit 95f721f

File tree

8 files changed

+51
-34
lines changed

8 files changed

+51
-34
lines changed

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
logger,
1616
parseUrl,
1717
safeJoin,
18+
SENTRY_XHR_DATA_KEY,
1819
severityLevelFromString,
1920
} from '@sentry/utils';
2021

@@ -225,12 +226,14 @@ function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level:
225226
function _xhrBreadcrumb(handlerData: HandlerData & HandlerDataXhr): void {
226227
const { startTimestamp, endTimestamp } = handlerData;
227228

229+
const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY];
230+
228231
// We only capture complete, non-sentry requests
229-
if (!startTimestamp || !endTimestamp || !handlerData.xhr.__sentry_xhr__) {
232+
if (!startTimestamp || !endTimestamp || !sentryXhrData) {
230233
return;
231234
}
232235

233-
const { method, url, status_code, body } = handlerData.xhr.__sentry_xhr__;
236+
const { method, url, status_code, body } = sentryXhrData;
234237

235238
const data: XhrBreadcrumbData = {
236239
method,

packages/integrations/src/httpclient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
addInstrumentationHandler,
1313
GLOBAL_OBJ,
1414
logger,
15+
SENTRY_XHR_DATA_KEY,
1516
supportsNativeFetch,
1617
} from '@sentry/utils';
1718

@@ -322,11 +323,13 @@ export class HttpClient implements Integration {
322323
(handlerData: HandlerDataXhr & { xhr: SentryWrappedXMLHttpRequest & XMLHttpRequest }) => {
323324
const { xhr } = handlerData;
324325

325-
if (!xhr.__sentry_xhr__) {
326+
const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
327+
328+
if (!sentryXhrData) {
326329
return;
327330
}
328331

329-
const { method, request_headers: headers } = xhr.__sentry_xhr__;
332+
const { method, request_headers: headers } = sentryXhrData;
330333

331334
if (!method) {
332335
return;

packages/replay/src/coreHandlers/handleXhr.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { HandlerDataXhr } from '@sentry/types';
2+
import { SENTRY_XHR_DATA_KEY } from '@sentry/utils';
23

34
import type { NetworkRequestData, ReplayContainer, ReplayPerformanceEntry } from '../types';
45
import { addNetworkBreadcrumb } from './util/addNetworkBreadcrumb';
@@ -7,12 +8,14 @@ import { addNetworkBreadcrumb } from './util/addNetworkBreadcrumb';
78
export function handleXhr(handlerData: HandlerDataXhr): ReplayPerformanceEntry<NetworkRequestData> | null {
89
const { startTimestamp, endTimestamp, xhr } = handlerData;
910

10-
if (!startTimestamp || !endTimestamp || !xhr.__sentry_xhr__) {
11+
const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
12+
13+
if (!startTimestamp || !endTimestamp || !sentryXhrData) {
1114
return null;
1215
}
1316

1417
// This is only used as a fallback, so we know the body sizes are never set here
15-
const { method, url, status_code: statusCode } = xhr.__sentry_xhr__;
18+
const { method, url, status_code: statusCode } = sentryXhrData;
1619

1720
if (url === undefined) {
1821
return null;

packages/replay/test/unit/coreHandlers/handleXhr.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { HandlerDataXhr, SentryWrappedXMLHttpRequest, SentryXhrData } from '@sentry/types';
2+
import { SENTRY_XHR_DATA_KEY } from '@sentry/utils';
23

34
import { handleXhr } from '../../../src/coreHandlers/handleXhr';
45

56
const DEFAULT_DATA: HandlerDataXhr = {
67
args: ['GET', '/api/0/organizations/sentry/'],
78
xhr: {
8-
__sentry_xhr__: {
9+
[SENTRY_XHR_DATA_KEY]: {
910
method: 'GET',
1011
url: '/api/0/organizations/sentry/',
1112
status_code: 200,
@@ -45,8 +46,8 @@ describe('Unit | coreHandlers | handleXhr', () => {
4546
...DEFAULT_DATA,
4647
xhr: {
4748
...DEFAULT_DATA.xhr,
48-
__sentry_xhr__: {
49-
...(DEFAULT_DATA.xhr.__sentry_xhr__ as SentryXhrData),
49+
[SENTRY_XHR_DATA_KEY]: {
50+
...(DEFAULT_DATA.xhr[SENTRY_XHR_DATA_KEY] as SentryXhrData),
5051
request_body_size: 123,
5152
response_body_size: 456,
5253
},

packages/tracing-internal/src/browser/request.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
BAGGAGE_HEADER_NAME,
77
dynamicSamplingContextToSentryBaggageHeader,
88
isInstanceOf,
9+
SENTRY_XHR_DATA_KEY,
910
stringMatchesSomePattern,
1011
} from '@sentry/utils';
1112

@@ -74,7 +75,7 @@ export interface FetchData {
7475
/** Data returned from XHR request */
7576
export interface XHRData {
7677
xhr?: {
77-
__sentry_xhr__?: {
78+
[SENTRY_XHR_DATA_KEY]?: {
7879
method: string;
7980
url: string;
8081
status_code: number;
@@ -297,24 +298,25 @@ export function xhrCallback(
297298
shouldAttachHeaders: (url: string) => boolean,
298299
spans: Record<string, Span>,
299300
): void {
301+
const xhr = handlerData.xhr;
302+
const sentryXhrData = xhr && xhr[SENTRY_XHR_DATA_KEY];
303+
300304
if (
301305
!hasTracingEnabled() ||
302-
(handlerData.xhr && handlerData.xhr.__sentry_own_request__) ||
303-
!(handlerData.xhr && handlerData.xhr.__sentry_xhr__ && shouldCreateSpan(handlerData.xhr.__sentry_xhr__.url))
306+
(xhr && xhr.__sentry_own_request__) ||
307+
!(xhr && sentryXhrData && shouldCreateSpan(sentryXhrData.url))
304308
) {
305309
return;
306310
}
307311

308-
const xhr = handlerData.xhr.__sentry_xhr__;
309-
310312
// check first if the request has finished and is tracked by an existing span which should now end
311313
if (handlerData.endTimestamp) {
312-
const spanId = handlerData.xhr.__sentry_xhr_span_id__;
314+
const spanId = xhr.__sentry_xhr_span_id__;
313315
if (!spanId) return;
314316

315317
const span = spans[spanId];
316318
if (span) {
317-
span.setHttpStatus(xhr.status_code);
319+
span.setHttpStatus(sentryXhrData.status_code);
318320
span.finish();
319321

320322
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
@@ -330,21 +332,21 @@ export function xhrCallback(
330332
if (currentSpan && activeTransaction) {
331333
const span = currentSpan.startChild({
332334
data: {
333-
...xhr.data,
335+
...sentryXhrData.data,
334336
type: 'xhr',
335-
method: xhr.method,
336-
url: xhr.url,
337+
method: sentryXhrData.method,
338+
url: sentryXhrData.url,
337339
},
338-
description: `${xhr.method} ${xhr.url}`,
340+
description: `${sentryXhrData.method} ${sentryXhrData.url}`,
339341
op: 'http.client',
340342
});
341343

342-
handlerData.xhr.__sentry_xhr_span_id__ = span.spanId;
343-
spans[handlerData.xhr.__sentry_xhr_span_id__] = span;
344+
xhr.__sentry_xhr_span_id__ = span.spanId;
345+
spans[xhr.__sentry_xhr_span_id__] = span;
344346

345-
if (handlerData.xhr.setRequestHeader && shouldAttachHeaders(handlerData.xhr.__sentry_xhr__.url)) {
347+
if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url)) {
346348
try {
347-
handlerData.xhr.setRequestHeader('sentry-trace', span.toTraceparent());
349+
xhr.setRequestHeader('sentry-trace', span.toTraceparent());
348350

349351
const dynamicSamplingContext = activeTransaction.getDynamicSamplingContext();
350352
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
@@ -353,7 +355,7 @@ export function xhrCallback(
353355
// From MDN: "If this method is called several times with the same header, the values are merged into one single request header."
354356
// We can therefore simply set a baggage header without checking what was there before
355357
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
356-
handlerData.xhr.setRequestHeader(BAGGAGE_HEADER_NAME, sentryBaggageHeader);
358+
xhr.setRequestHeader(BAGGAGE_HEADER_NAME, sentryBaggageHeader);
357359
}
358360
} catch (_) {
359361
// Error: InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

packages/tracing-internal/test/browser/request.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable deprecation/deprecation */
22
import * as sentryCore from '@sentry/core';
33
import * as utils from '@sentry/utils';
4+
import { SENTRY_XHR_DATA_KEY } from '@sentry/utils';
45

56
import type { Transaction } from '../../../tracing/src';
67
import { addExtensionMethods, Span, spanStatusfromHttpCode } from '../../../tracing/src';
@@ -234,7 +235,7 @@ describe('callbacks', () => {
234235
beforeEach(() => {
235236
xhrHandlerData = {
236237
xhr: {
237-
__sentry_xhr__: {
238+
[SENTRY_XHR_DATA_KEY]: {
238239
method: 'GET',
239240
url: 'http://dogs.are.great/',
240241
status_code: 200,
@@ -328,7 +329,7 @@ describe('callbacks', () => {
328329
...xhrHandlerData,
329330
endTimestamp,
330331
};
331-
postRequestXHRHandlerData.xhr!.__sentry_xhr__!.status_code = 404;
332+
postRequestXHRHandlerData.xhr![SENTRY_XHR_DATA_KEY]!.status_code = 404;
332333

333334
// triggered by response coming back
334335
xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
@@ -342,7 +343,7 @@ describe('callbacks', () => {
342343
const postRequestXHRHandlerData = {
343344
...{
344345
xhr: {
345-
__sentry_xhr__: xhrHandlerData.xhr?.__sentry_xhr__,
346+
[SENTRY_XHR_DATA_KEY]: xhrHandlerData.xhr?.[SENTRY_XHR_DATA_KEY],
346347
},
347348
},
348349
startTimestamp,

packages/types/src/instrument.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
type XHRSendInput = unknown;
55

66
export interface SentryWrappedXMLHttpRequest {
7-
__sentry_xhr__?: SentryXhrData;
7+
__sentry_xhr_v2__?: SentryXhrData;
88
__sentry_own_request__?: boolean;
99
}
1010

11+
// WARNING: When the shape of this type is changed bump the version in `SentryWrappedXMLHttpRequest`
1112
export interface SentryXhrData {
1213
method?: string;
1314
url?: string;

packages/utils/src/instrument.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { getGlobalObject } from './worldwide';
1919
// eslint-disable-next-line deprecation/deprecation
2020
const WINDOW = getGlobalObject<Window>();
2121

22+
export const SENTRY_XHR_DATA_KEY = '__sentry_xhr_v2__';
23+
2224
export type InstrumentHandlerType =
2325
| 'console'
2426
| 'dom'
@@ -244,7 +246,7 @@ function instrumentXHR(): void {
244246
fill(xhrproto, 'open', function (originalOpen: () => void): () => void {
245247
return function (this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: any[]): void {
246248
const url = args[1];
247-
const xhrInfo: SentryXhrData = (this.__sentry_xhr__ = {
249+
const xhrInfo: SentryXhrData = (this[SENTRY_XHR_DATA_KEY] = {
248250
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
249251
method: isString(args[0]) ? args[0].toUpperCase() : args[0],
250252
url: args[1],
@@ -259,7 +261,7 @@ function instrumentXHR(): void {
259261

260262
const onreadystatechangeHandler: () => void = () => {
261263
// For whatever reason, this is not the same instance here as from the outer method
262-
const xhrInfo = this.__sentry_xhr__;
264+
const xhrInfo = this[SENTRY_XHR_DATA_KEY];
263265

264266
if (!xhrInfo) {
265267
return;
@@ -301,7 +303,7 @@ function instrumentXHR(): void {
301303
return function (this: SentryWrappedXMLHttpRequest, ...setRequestHeaderArgs: unknown[]): void {
302304
const [header, value] = setRequestHeaderArgs as [string, string];
303305

304-
const xhrInfo = this.__sentry_xhr__;
306+
const xhrInfo = this[SENTRY_XHR_DATA_KEY];
305307

306308
if (xhrInfo) {
307309
xhrInfo.request_headers[header] = value;
@@ -317,8 +319,9 @@ function instrumentXHR(): void {
317319

318320
fill(xhrproto, 'send', function (originalSend: () => void): () => void {
319321
return function (this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: any[]): void {
320-
if (this.__sentry_xhr__ && args[0] !== undefined) {
321-
this.__sentry_xhr__.body = args[0];
322+
const sentryXhrData = this[SENTRY_XHR_DATA_KEY];
323+
if (sentryXhrData && args[0] !== undefined) {
324+
sentryXhrData.body = args[0];
322325
}
323326

324327
triggerHandlers('xhr', {

0 commit comments

Comments
 (0)