diff --git a/packages/browser-utils/src/instrument/xhr.ts b/packages/browser-utils/src/instrument/xhr.ts index e9933ed53a4a..f46ecd67b0b6 100644 --- a/packages/browser-utils/src/instrument/xhr.ts +++ b/packages/browser-utils/src/instrument/xhr.ts @@ -15,17 +15,14 @@ type WindowWithXhr = Window & { XMLHttpRequest?: typeof XMLHttpRequest }; * Use at your own risk, this might break without changelog notice, only used internally. * @hidden */ -export function addXhrInstrumentationHandler( - handler: (data: HandlerDataXhr) => void, - httpClientInstrumented?: boolean, -): void { +export function addXhrInstrumentationHandler(handler: (data: HandlerDataXhr) => void): void { const type = 'xhr'; addHandler(type, handler); - maybeInstrument(type, () => instrumentXHR(httpClientInstrumented)); + maybeInstrument(type, instrumentXHR); } /** Exported only for tests. */ -export function instrumentXHR(httpClientInstrumented: boolean = false): void { +export function instrumentXHR(): void { if (!(WINDOW as WindowWithXhr).XMLHttpRequest) { return; } @@ -85,7 +82,7 @@ export function instrumentXHR(httpClientInstrumented: boolean = false): void { endTimestamp: timestampInSeconds() * 1000, startTimestamp, xhr: xhrOpenThisArg, - error: httpClientInstrumented ? virtualError : undefined, + stack: virtualError.stack, }; triggerHandlers('xhr', handlerData); } diff --git a/packages/browser/src/integrations/httpclient.ts b/packages/browser/src/integrations/httpclient.ts index 3690c62679ab..15c8b3412963 100644 --- a/packages/browser/src/integrations/httpclient.ts +++ b/packages/browser/src/integrations/httpclient.ts @@ -70,7 +70,7 @@ function _fetchResponseHandler( requestInfo: RequestInfo, response: Response, requestInit?: RequestInit, - error?: unknown, + stack?: string, ): void { if (_shouldCaptureResponse(options, response.status, response.url)) { const request = _getRequest(requestInfo, requestInit); @@ -90,7 +90,7 @@ function _fetchResponseHandler( responseHeaders, requestCookies, responseCookies, - stacktrace: error instanceof Error ? error.stack : undefined, + stacktrace: stack, }); captureEvent(event); @@ -129,7 +129,7 @@ function _xhrResponseHandler( xhr: XMLHttpRequest, method: string, headers: Record, - error?: unknown, + stack?: string, ): void { if (_shouldCaptureResponse(options, xhr.status, xhr.responseURL)) { let requestHeaders, responseCookies, responseHeaders; @@ -162,7 +162,7 @@ function _xhrResponseHandler( // Can't access request cookies from XHR responseHeaders, responseCookies, - stacktrace: error instanceof Error ? error.stack : undefined, + stacktrace: stack, }); captureEvent(event); @@ -287,24 +287,20 @@ function _wrapFetch(client: Client, options: HttpClientOptions): void { return; } - addFetchInstrumentationHandler( - handlerData => { - if (getClient() !== client) { - return; - } + addFetchInstrumentationHandler(handlerData => { + if (getClient() !== client) { + return; + } - const { response, args } = handlerData; - const [requestInfo, requestInit] = args as [RequestInfo, RequestInit | undefined]; + const { response, args } = handlerData; + const [requestInfo, requestInit] = args as [RequestInfo, RequestInit | undefined]; - if (!response) { - return; - } + if (!response) { + return; + } - _fetchResponseHandler(options, requestInfo, response as Response, requestInit, handlerData.error); - }, - false, - true, - ); + _fetchResponseHandler(options, requestInfo, response as Response, requestInit, handlerData.stack); + }, false); } /** @@ -331,11 +327,11 @@ function _wrapXHR(client: Client, options: HttpClientOptions): void { const { method, request_headers: headers } = sentryXhrData; try { - _xhrResponseHandler(options, xhr, method, headers, handlerData.error); + _xhrResponseHandler(options, xhr, method, headers, handlerData.stack); } catch (e) { DEBUG_BUILD && logger.warn('Error while extracting response event form XHR response', e); } - }, true); + }); } /** diff --git a/packages/core/src/types-hoist/instrument.ts b/packages/core/src/types-hoist/instrument.ts index f472a663c5d5..f779b2096597 100644 --- a/packages/core/src/types-hoist/instrument.ts +++ b/packages/core/src/types-hoist/instrument.ts @@ -32,7 +32,7 @@ export interface HandlerDataXhr { xhr: SentryWrappedXMLHttpRequest; startTimestamp?: number; endTimestamp?: number; - error?: unknown; + stack?: string; } interface SentryFetchData { @@ -57,6 +57,7 @@ export interface HandlerDataFetch { headers: WebFetchHeaders; }; error?: unknown; + stack?: string; } export interface HandlerDataDom { diff --git a/packages/core/src/utils-hoist/instrument/fetch.ts b/packages/core/src/utils-hoist/instrument/fetch.ts index a7b1a4972252..c822066a7c2d 100644 --- a/packages/core/src/utils-hoist/instrument/fetch.ts +++ b/packages/core/src/utils-hoist/instrument/fetch.ts @@ -21,11 +21,10 @@ type FetchResource = string | { toString(): string } | { url: string }; export function addFetchInstrumentationHandler( handler: (data: HandlerDataFetch) => void, skipNativeFetchCheck?: boolean, - httpClientInstrumented?: boolean, ): void { const type = 'fetch'; addHandler(type, handler); - maybeInstrument(type, () => instrumentFetch(undefined, skipNativeFetchCheck, httpClientInstrumented)); + maybeInstrument(type, () => instrumentFetch(undefined, skipNativeFetchCheck)); } /** @@ -42,17 +41,23 @@ export function addFetchEndInstrumentationHandler(handler: (data: HandlerDataFet maybeInstrument(type, () => instrumentFetch(streamHandler)); } -function instrumentFetch( - onFetchResolved?: (response: Response) => void, - skipNativeFetchCheck: boolean = false, - httpClientInstrumented: boolean = false, -): void { +function instrumentFetch(onFetchResolved?: (response: Response) => void, skipNativeFetchCheck: boolean = false): void { if (skipNativeFetchCheck && !supportsNativeFetch()) { return; } fill(GLOBAL_OBJ, 'fetch', function (originalFetch: () => void): () => void { return function (...args: any[]): void { + // We capture the stack right here and not in the Promise error callback because Safari (and probably other + // browsers too) will wipe the stack trace up to this point, only leaving us with this file which is useless. + + // NOTE: If you are a Sentry user, and you are seeing this stack frame, + // it means the error, that was caused by your fetch call did not + // have a stack trace, so the SDK backfilled the stack trace so + // you can see which fetch call failed. + const virtualError = new Error(); + const virtualStackTrace = virtualError.stack; + const { method, url } = parseFetchArgs(args); const handlerData: HandlerDataFetch = { args, @@ -61,27 +66,16 @@ function instrumentFetch( url, }, startTimestamp: timestampInSeconds() * 1000, + stack: virtualStackTrace, }; // if there is no callback, fetch is instrumented directly - // if httpClientInstrumented is true, we are in the HttpClient instrumentation - // and we may need to capture the stacktrace even when the fetch promise is resolved - if (!onFetchResolved && !httpClientInstrumented) { + if (!onFetchResolved) { triggerHandlers('fetch', { ...handlerData, }); } - // We capture the stack right here and not in the Promise error callback because Safari (and probably other - // browsers too) will wipe the stack trace up to this point, only leaving us with this file which is useless. - - // NOTE: If you are a Sentry user, and you are seeing this stack frame, - // it means the error, that was caused by your fetch call did not - // have a stack trace, so the SDK backfilled the stack trace so - // you can see which fetch call failed. - const virtualError = new Error(); - const virtualStackTrace = virtualError.stack; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return originalFetch.apply(GLOBAL_OBJ, args).then( async (response: Response) => { @@ -93,7 +87,6 @@ function instrumentFetch( ...handlerData, endTimestamp: timestampInSeconds() * 1000, response, - error: httpClientInstrumented ? virtualError : undefined, }); }