Skip to content

Conversation

@s1gr1d
Copy link
Member

@s1gr1d s1gr1d commented Dec 15, 2025

Previously, we only got the body from the fetch options. However, some people use the Request object which can also contain a body. As this body is a ReadableStream, we could only read that async.

With attachRawBodyFromRequest, the Request object is patched to include the raw body as a Symbol so it can be read synchronously.

Linear ticket (internal): https://linear.app/getsentry/issue/JS-1255/investigate-sdk-issues-causing-all-requests-to-default-to-get

@s1gr1d s1gr1d requested a review from a team as a code owner December 15, 2025 09:43
expect(actual).toBe(expected);
});

describe('valid types of body (in Request)', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note to reviewers: Those tests are copied from above, where those different types are tested without the Request object.

(request as any)[ORIGINAL_BODY] = init.body;
}

return request;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Request cloning doesn't preserve body tracking symbol

When a Request is created by cloning another Request (e.g., new Request(existingRequest) or new Request(existingRequest, { headers: {...} })), the ORIGINAL_BODY symbol is not copied to the new Request. The code only stores the body when init && 'body' in init && init.body != null, but when input is a Request object with the symbol already set and init doesn't have a body property, the new Request inherits the body stream but loses the symbol reference. This means body capture fails for cloned Requests.

Fix in Cursor Fix in Web

Copy link
Collaborator

@logaretm logaretm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one! Just one minor Q


// Preserve the prototype
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(GLOBAL_OBJ as any).Request.prototype = OriginalRequest.prototype;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I was wondering about instanceof checks.

Comment on lines +65 to +67
if (originalBody !== undefined) {
return originalBody;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: It is possible that the user creates a req object with a stream body, should we explicitly return undefined here or is it ignored safely at the caller?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we would also attach the stream. It's a very unlikely use case because it would mean that you create a ReadableStream yourself and attach it to a Request, which results in a double-nested readable stream 🤔

@github-actions
Copy link
Contributor

github-actions bot commented Dec 15, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.81 kB - -
@sentry/browser - with treeshaking flags 23.3 kB - -
@sentry/browser (incl. Tracing) 41.58 kB +0.06% +21 B 🔺
@sentry/browser (incl. Tracing, Profiling) 46.18 kB +0.06% +24 B 🔺
@sentry/browser (incl. Tracing, Replay) 80.16 kB +0.24% +186 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 69.9 kB +0.29% +198 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 84.85 kB +0.24% +199 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 97.08 kB +0.2% +186 B 🔺
@sentry/browser (incl. Feedback) 41.52 kB - -
@sentry/browser (incl. sendFeedback) 29.49 kB - -
@sentry/browser (incl. FeedbackAsync) 34.48 kB - -
@sentry/react 26.52 kB - -
@sentry/react (incl. Tracing) 43.78 kB +0.05% +21 B 🔺
@sentry/vue 29.27 kB - -
@sentry/vue (incl. Tracing) 43.39 kB +0.06% +23 B 🔺
@sentry/svelte 24.82 kB - -
CDN Bundle 27.23 kB - -
CDN Bundle (incl. Tracing) 42.22 kB - -
CDN Bundle (incl. Tracing, Replay) 78.93 kB +0.24% +186 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 84.39 kB +0.23% +191 B 🔺
CDN Bundle - uncompressed 80.02 kB - -
CDN Bundle (incl. Tracing) - uncompressed 125.37 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 241.91 kB +0.21% +505 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 254.67 kB +0.2% +505 B 🔺
@sentry/nextjs (client) 46 kB +0.05% +19 B 🔺
@sentry/sveltekit (client) 41.95 kB +0.06% +21 B 🔺
@sentry/node-core 51.6 kB - -
@sentry/node 161.48 kB - -
@sentry/node - without tracing 93.03 kB - -
@sentry/aws-serverless 108.54 kB - -

View base workflow run

return async () => {
globalThis.Request = OriginalRequest;
};
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test cleanup doesn't reset module-level instrumentation flag

The beforeAll cleanup only restores globalThis.Request but doesn't reset the module-level _isRequestInstrumented flag. After the "with patching" tests run, _isRequestInstrumented remains true even after cleanup. If these tests run again in the same process (e.g., in watch mode), subsequent calls to _INTERNAL_instrumentRequestInterface() will return early without patching because the flag is still true, but globalThis.Request has been restored to the original. This causes the patching to silently not occur, making the "with patching" tests fail on re-runs.

Fix in Cursor Fix in Web

@github-actions
Copy link
Contributor

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,847 - 8,535 +4%
GET With Sentry 1,673 19% 1,608 +4%
GET With Sentry (error only) 6,067 69% 5,850 +4%
POST Baseline 1,189 - 1,166 +2%
POST With Sentry 591 50% 546 +8%
POST With Sentry (error only) 1,058 89% 1,023 +3%
MYSQL Baseline 3,308 - 3,188 +4%
MYSQL With Sentry 465 14% 427 +9%
MYSQL With Sentry (error only) 2,686 81% 2,607 +3%

View base workflow run

Copy link
Member

@chargome chargome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, implementation LGTM!

Only downside is the bundle size hit for an option that only few users will ever opt-in to I assume.

@s1gr1d s1gr1d merged commit f3fbcd9 into develop Dec 17, 2025
159 checks passed
@s1gr1d s1gr1d deleted the sig/replay-raw-request-bodies branch December 17, 2025 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants