Skip to content

Commit 4a6256e

Browse files
authored
fix(replay): Ensure network breadcrumbs have all data (#7491)
1 parent 6a2fdac commit 4a6256e

File tree

12 files changed

+576
-167
lines changed

12 files changed

+576
-167
lines changed

packages/browser-integration-tests/suites/replay/requests/subject.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ document.getElementById('go-background').addEventListener('click', () => {
66
});
77

88
document.getElementById('fetch').addEventListener('click', () => {
9-
fetch('https://example.com');
9+
fetch('https://example.com', { method: 'POST', body: 'foo' });
1010
});
1111

1212
document.getElementById('xhr').addEventListener('click', () => {

packages/browser-integration-tests/utils/replayEventTemplates.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,23 +132,26 @@ export const expectedFPPerformanceSpan = {
132132

133133
export const expectedFetchPerformanceSpan = {
134134
op: 'resource.fetch',
135-
description: expect.any(String),
135+
description: 'https://example.com',
136136
startTimestamp: expect.any(Number),
137137
endTimestamp: expect.any(Number),
138138
data: {
139-
method: expect.any(String),
140-
statusCode: expect.any(Number),
139+
method: 'POST',
140+
statusCode: 200,
141+
responseBodySize: 11,
142+
requestBodySize: 3,
141143
},
142144
};
143145

144146
export const expectedXHRPerformanceSpan = {
145147
op: 'resource.xhr',
146-
description: expect.any(String),
148+
description: 'https://example.com',
147149
startTimestamp: expect.any(Number),
148150
endTimestamp: expect.any(Number),
149151
data: {
150-
method: expect.any(String),
151-
statusCode: expect.any(Number),
152+
method: 'GET',
153+
statusCode: 200,
154+
responseBodySize: 11,
152155
},
153156
};
154157

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
/* eslint-disable max-lines */
33
import { getCurrentHub } from '@sentry/core';
44
import type { Event as SentryEvent, HandlerDataFetch, HandlerDataXhr, Integration } from '@sentry/types';
5+
import type {
6+
FetchBreadcrumbData,
7+
FetchBreadcrumbHint,
8+
XhrBreadcrumbData,
9+
XhrBreadcrumbHint,
10+
} from '@sentry/types/build/types/breadcrumb';
511
import {
612
addInstrumentationHandler,
713
getEventDescription,
@@ -217,36 +223,46 @@ function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level:
217223
* Creates breadcrumbs from XHR API calls
218224
*/
219225
function _xhrBreadcrumb(handlerData: HandlerData & HandlerDataXhr): void {
226+
const { startTimestamp, endTimestamp } = handlerData;
227+
220228
// We only capture complete, non-sentry requests
221-
if (!handlerData.endTimestamp || !handlerData.xhr.__sentry_xhr__) {
229+
if (!startTimestamp || !endTimestamp || !handlerData.xhr.__sentry_xhr__) {
222230
return;
223231
}
224232

225233
const { method, url, status_code, body } = handlerData.xhr.__sentry_xhr__;
226234

235+
const data: XhrBreadcrumbData = {
236+
method,
237+
url,
238+
status_code,
239+
};
240+
241+
const hint: XhrBreadcrumbHint = {
242+
xhr: handlerData.xhr,
243+
input: body,
244+
startTimestamp,
245+
endTimestamp,
246+
};
247+
227248
getCurrentHub().addBreadcrumb(
228249
{
229250
category: 'xhr',
230-
data: {
231-
method,
232-
url,
233-
status_code,
234-
},
251+
data,
235252
type: 'http',
236253
},
237-
{
238-
xhr: handlerData.xhr,
239-
input: body,
240-
},
254+
hint,
241255
);
242256
}
243257

244258
/**
245259
* Creates breadcrumbs from fetch API calls
246260
*/
247261
function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch & { response?: Response }): void {
262+
const { startTimestamp, endTimestamp } = handlerData;
263+
248264
// We only capture complete fetch requests
249-
if (!handlerData.endTimestamp) {
265+
if (!endTimestamp) {
250266
return;
251267
}
252268

@@ -256,32 +272,41 @@ function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch & { respon
256272
}
257273

258274
if (handlerData.error) {
275+
const data: FetchBreadcrumbData = handlerData.fetchData;
276+
const hint: FetchBreadcrumbHint = {
277+
data: handlerData.error,
278+
input: handlerData.args,
279+
startTimestamp,
280+
endTimestamp,
281+
};
282+
259283
getCurrentHub().addBreadcrumb(
260284
{
261285
category: 'fetch',
262-
data: handlerData.fetchData,
286+
data,
263287
level: 'error',
264288
type: 'http',
265289
},
266-
{
267-
data: handlerData.error,
268-
input: handlerData.args,
269-
},
290+
hint,
270291
);
271292
} else {
293+
const data: FetchBreadcrumbData = {
294+
...handlerData.fetchData,
295+
status_code: handlerData.response && handlerData.response.status,
296+
};
297+
const hint: FetchBreadcrumbHint = {
298+
input: handlerData.args,
299+
response: handlerData.response,
300+
startTimestamp,
301+
endTimestamp,
302+
};
272303
getCurrentHub().addBreadcrumb(
273304
{
274305
category: 'fetch',
275-
data: {
276-
...handlerData.fetchData,
277-
status_code: handlerData.response && handlerData.response.status,
278-
},
306+
data,
279307
type: 'http',
280308
},
281-
{
282-
input: handlerData.args,
283-
response: handlerData.response,
284-
},
309+
hint,
285310
);
286311
}
287312
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { ReplayContainer, ReplayPerformanceEntry } from '../types';
2+
import { createPerformanceSpans } from '../util/createPerformanceSpans';
3+
import { shouldFilterRequest } from '../util/shouldFilterRequest';
4+
5+
/** Add a performance entry breadcrumb */
6+
export function addNetworkBreadcrumb(replay: ReplayContainer, result: ReplayPerformanceEntry | null): void {
7+
if (!replay.isEnabled()) {
8+
return;
9+
}
10+
11+
if (result === null) {
12+
return;
13+
}
14+
15+
if (shouldFilterRequest(replay, result.name)) {
16+
return;
17+
}
18+
19+
replay.addUpdate(() => {
20+
createPerformanceSpans(replay, [result]);
21+
// Returning true will cause `addUpdate` to not flush
22+
// We do not want network requests to cause a flush. This will prevent
23+
// recurring/polling requests from keeping the replay session alive.
24+
return true;
25+
});
26+
}

packages/replay/src/coreHandlers/handleFetch.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { HandlerDataFetch } from '@sentry/types';
22

33
import type { ReplayContainer, ReplayPerformanceEntry } from '../types';
4-
import { createPerformanceSpans } from '../util/createPerformanceSpans';
5-
import { shouldFilterRequest } from '../util/shouldFilterRequest';
4+
import { addNetworkBreadcrumb } from './addNetworkBreadcrumb';
65

76
/** only exported for tests */
87
export function handleFetch(handlerData: HandlerDataFetch): null | ReplayPerformanceEntry {
@@ -39,20 +38,6 @@ export function handleFetchSpanListener(replay: ReplayContainer): (handlerData:
3938

4039
const result = handleFetch(handlerData);
4140

42-
if (result === null) {
43-
return;
44-
}
45-
46-
if (shouldFilterRequest(replay, result.name)) {
47-
return;
48-
}
49-
50-
replay.addUpdate(() => {
51-
createPerformanceSpans(replay, [result]);
52-
// Returning true will cause `addUpdate` to not flush
53-
// We do not want network requests to cause a flush. This will prevent
54-
// recurring/polling requests from keeping the replay session alive.
55-
return true;
56-
});
41+
addNetworkBreadcrumb(replay, result);
5742
};
5843
}

0 commit comments

Comments
 (0)