Skip to content

Commit 1014da2

Browse files
authored
feat(utils): Backfill stack trace on fetch errors if it is missing (#12389)
1 parent e8e86d0 commit 1014da2

File tree

6 files changed

+45
-8
lines changed

6 files changed

+45
-8
lines changed

dev-packages/browser-integration-tests/playwright.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const config: PlaywrightTestConfig = {
1111
testMatch: /test.ts/,
1212

1313
use: {
14-
trace: process.env.CI ? 'retain-on-failure' : 'off',
14+
trace: 'retain-on-failure',
1515
},
1616

1717
projects: [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fetch('http://localhost:123/fake/endpoint/that/will/fail');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers';
6+
7+
sentryTest('should create errors with stack traces for failing fetch calls', async ({ getLocalTestPath, page }) => {
8+
const url = await getLocalTestPath({ testDir: __dirname });
9+
10+
const envelopes = await getMultipleSentryEnvelopeRequests<Event>(page, 3, { url, timeout: 10000 });
11+
const errorEvent = envelopes.find(event => !event.type)!;
12+
expect(errorEvent?.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThan(0);
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
});

dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@ sentryTest('should create spans for fetch requests', async ({ getLocalTestPath,
1717

1818
// We will wait 500ms for all envelopes to be sent. Generally, in all browsers, the last sent
1919
// envelope contains tracing data.
20-
21-
// If we are on FF or webkit:
22-
// 1st envelope contains CORS error
23-
// 2nd envelope contains the tracing data we want to check here
24-
const envelopes = await getMultipleSentryEnvelopeRequests<Event>(page, 2, { url, timeout: 10000 });
25-
const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers
20+
const envelopes = await getMultipleSentryEnvelopeRequests<Event>(page, 4, { url, timeout: 10000 });
21+
const tracingEvent = envelopes.find(event => event.type === 'transaction')!; // last envelope contains tracing data on all browsers
2622

2723
const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client');
2824

packages/utils/src/instrument/fetch.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import type { HandlerDataFetch } from '@sentry/types';
33

4-
import { fill } from '../object';
4+
import { isError } from '../is';
5+
import { addNonEnumerableProperty, fill } from '../object';
56
import { supportsNativeFetch } from '../supports';
67
import { timestampInSeconds } from '../time';
78
import { GLOBAL_OBJ } from '../worldwide';
@@ -45,6 +46,15 @@ function instrumentFetch(): void {
4546
...handlerData,
4647
});
4748

49+
// We capture the stack right here and not in the Promise error callback because Safari (and probably other
50+
// browsers too) will wipe the stack trace up to this point, only leaving us with this file which is useless.
51+
52+
// NOTE: If you are a Sentry user, and you are seeing this stack frame,
53+
// it means the error, that was caused by your fetch call did not
54+
// have a stack trace, so the SDK backfilled the stack trace so
55+
// you can see which fetch call failed.
56+
const virtualStackTrace = new Error().stack;
57+
4858
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
4959
return originalFetch.apply(GLOBAL_OBJ, args).then(
5060
(response: Response) => {
@@ -65,6 +75,16 @@ function instrumentFetch(): void {
6575
};
6676

6777
triggerHandlers('fetch', erroredHandlerData);
78+
79+
if (isError(error) && error.stack === undefined) {
80+
// NOTE: If you are a Sentry user, and you are seeing this stack frame,
81+
// it means the error, that was caused by your fetch call did not
82+
// have a stack trace, so the SDK backfilled the stack trace so
83+
// you can see which fetch call failed.
84+
error.stack = virtualStackTrace;
85+
addNonEnumerableProperty(error, 'framesToPop', 1);
86+
}
87+
6888
// NOTE: If you are a Sentry user, and you are seeing this stack frame,
6989
// it means the sentry.javascript SDK caught an error invoking your application code.
7090
// This is expected behavior and NOT indicative of a bug with sentry.javascript.

0 commit comments

Comments
 (0)