From 8c9188f188ffa0c9aea1483c2f693bb23703e70a Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Tue, 27 Jan 2026 21:57:35 +0000 Subject: [PATCH] fix: preserve fetch stack traces --- index-fetch.js | 28 +++++++++++++++++++++++--- index.js | 28 +++++++++++++++++++++++--- test/fetch/client-error-stack-trace.js | 12 +++++------ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/index-fetch.js b/index-fetch.js index 8f5bb6ceae2..0180b5865b9 100644 --- a/index-fetch.js +++ b/index-fetch.js @@ -4,11 +4,33 @@ const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global') const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent') const fetchImpl = require('./lib/web/fetch').fetch +function appendFetchStackTrace (err, filename) { + if (!err || typeof err !== 'object') { + return + } + + const stack = typeof err.stack === 'string' ? err.stack : '' + const normalizedFilename = filename.replace(/\\/g, '/') + + if (stack && (stack.includes(filename) || stack.includes(normalizedFilename))) { + return + } + + const capture = {} + Error.captureStackTrace(capture, appendFetchStackTrace) + + if (!capture.stack) { + return + } + + const captureLines = capture.stack.split('\n').slice(1).join('\n') + + err.stack = stack ? `${stack}\n${captureLines}` : capture.stack +} + module.exports.fetch = function fetch (init, options = undefined) { return fetchImpl(init, options).catch(err => { - if (err && typeof err === 'object') { - Error.captureStackTrace(err) - } + appendFetchStackTrace(err, __filename) throw err }) } diff --git a/index.js b/index.js index 14f439a2334..0dc12d62ecd 100644 --- a/index.js +++ b/index.js @@ -121,11 +121,33 @@ module.exports.getGlobalDispatcher = getGlobalDispatcher const fetchImpl = require('./lib/web/fetch').fetch +function appendFetchStackTrace (err, filename) { + if (!err || typeof err !== 'object') { + return + } + + const stack = typeof err.stack === 'string' ? err.stack : '' + const normalizedFilename = filename.replace(/\\/g, '/') + + if (stack && (stack.includes(filename) || stack.includes(normalizedFilename))) { + return + } + + const capture = {} + Error.captureStackTrace(capture, appendFetchStackTrace) + + if (!capture.stack) { + return + } + + const captureLines = capture.stack.split('\n').slice(1).join('\n') + + err.stack = stack ? `${stack}\n${captureLines}` : capture.stack +} + module.exports.fetch = function fetch (init, options = undefined) { return fetchImpl(init, options).catch(err => { - if (err && typeof err === 'object') { - Error.captureStackTrace(err) - } + appendFetchStackTrace(err, __filename) throw err }) } diff --git a/test/fetch/client-error-stack-trace.js b/test/fetch/client-error-stack-trace.js index 812f1076793..daa4838a34e 100644 --- a/test/fetch/client-error-stack-trace.js +++ b/test/fetch/client-error-stack-trace.js @@ -18,9 +18,9 @@ test('FETCH: request errors and prints trimmed stack trace', async (t) => { } catch (error) { const stackLines = error.stack.split('\n') t.assert.ok(stackLines[0].includes('TypeError: fetch failed')) - t.assert.ok(stackLines[1].includes(`${projectFolder}${sep}index.js`)) - t.assert.ok(stackLines[2].includes('at process.processTicksAndRejections')) - t.assert.ok(stackLines[3].includes(`at async TestContext. (${__filename}`)) + t.assert.ok(stackLines.some(line => line.includes(`lib${sep}web${sep}fetch${sep}index.js`))) + t.assert.ok(stackLines.some(line => line.includes(`${projectFolder}${sep}index.js`))) + t.assert.ok(stackLines.some(line => line.includes(__filename))) } }) @@ -30,8 +30,8 @@ test('FETCH-index: request errors and prints trimmed stack trace', async (t) => } catch (error) { const stackLines = error.stack.split('\n') t.assert.ok(stackLines[0].includes('TypeError: fetch failed')) - t.assert.ok(stackLines[1].includes(`${projectFolder}${sep}index-fetch.js`)) - t.assert.ok(stackLines[2].includes('at process.processTicksAndRejections')) - t.assert.ok(stackLines[3].includes(`at async TestContext. (${__filename}`)) + t.assert.ok(stackLines.some(line => line.includes(`lib${sep}web${sep}fetch${sep}index.js`))) + t.assert.ok(stackLines.some(line => line.includes(`${projectFolder}${sep}index-fetch.js`))) + t.assert.ok(stackLines.some(line => line.includes(__filename))) } })