Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(replay): Capture request/response body size for xhr/fetch breadcrumbs #7407

Merged
merged 1 commit into from
Mar 16, 2023

Conversation

mydea
Copy link
Member

@mydea mydea commented Mar 10, 2023

This ensures that in Replay we capture more details for xhr/fetch breadcrumbs.

It will enrich the breadcrumbs with request_body_size and response_body_size fields.

This works as follows:

  • For request body, we can access the body relatively easily. We handle different body types reasonably well.
  • For responses, we take the value of the Content-Length header, if available. No further processing needed!
    • For xhr, we fall back to getting the response size from the actual response.
    • For fetch, we cannot easily provide a fallback - see below.

For replay, we pass these through as requestBodySize and responseBodySize - we use camelCase there 馃槤

For regular breadcrumbs, this looks as follow in the current UI:

Screenshot 2023-03-10 at 11 52 29

I refined types a bit to make this easier, we had a few places where we were still using incomplete types (and for replay, duplicating the types sometimes). This should now be reasonably type safe.

Caveats / notes

  • For fetch we do not get the response body size if the Content-Length header is not set. The reason for this is that this is async in fetch - we'd need to do:
const cloned = await response.clone(); // can only access text once, so need to clone the whole response
const body = await cloned.blob();
return getBodySize(body);

Closes #7373

@mydea mydea self-assigned this Mar 10, 2023
function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRequest }): void {
if (handlerData.endTimestamp) {
// We only capture complete, non-sentry requests
if (handlerData.xhr.__sentry_own_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.

This is actually checked in the "original" breadcrumb integration in browser already, so no need to check this here again IMHO:
https://github.com/getsentry/sentry-javascript/blob/develop/packages/browser/src/integrations/breadcrumbs.ts#L222

Actually the same is also true for the endTimestamp/sentry_xhr fields, but there it makes more sense to guard them defensively again, I'd say.


if (!handlerData.endTimestamp) {
if (!startTimestamp || !endTimestamp || !xhr.__sentry_xhr__) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Not 100% sure, but I don't think the "moving timestamps around & checking differnent places" stuff is really needed here (anymore), this should always be set, and IMHO if one of these fails we just bail out.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, let's try it this way.

const onreadystatechangeHandler = function (): void {
if (xhr.readyState === 4) {
const onreadystatechangeHandler: () => void = () => {
// For whatever reason, this is not the same instance here as from the outer method
Copy link
Member Author

Choose a reason for hiding this comment

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

This is extremely weird and I wasted a lot of time figuring out why this is happening, to no avail.

basically, it seems that when we just assign to xhrInfo from the outer scope, this is not the same as this.sentry_xhr, even though the this remains the same, and I don't see any other place where we'd set this 馃槵

Copy link
Member

Choose a reason for hiding this comment

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

Ooof no idea.. but given that integration tests are passing, I'd say let's just go with it...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2023

size-limit report 馃摝

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 20.48 KB (+0.37% 馃敽)
@sentry/browser - ES5 CDN Bundle (minified) 63.49 KB (+0.31% 馃敽)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 19.05 KB (+0.34% 馃敽)
@sentry/browser - ES6 CDN Bundle (minified) 56.39 KB (+0.32% 馃敽)
@sentry/browser - Webpack (gzipped + minified) 20.7 KB (+0.26% 馃敽)
@sentry/browser - Webpack (minified) 67.61 KB (+0.27% 馃敽)
@sentry/react - Webpack (gzipped + minified) 20.73 KB (+0.25% 馃敽)
@sentry/nextjs Client - Webpack (gzipped + minified) 52.19 KB (+0.14% 馃敽)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 33.76 KB (+0.31% 馃敽)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 26.08 KB (+0.27% 馃敽)
@sentry/replay ES6 CDN Bundle (gzipped + minified) 43.87 KB (+0.98% 馃敽)
@sentry/replay - Webpack (gzipped + minified) 37.95 KB (+1.24% 馃敽)
@sentry/browser + @sentry/tracing + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 62.06 KB (+0.77% 馃敽)
@sentry/browser + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 55.12 KB (+0.85% 馃敽)

@mydea
Copy link
Member Author

mydea commented Mar 10, 2023

I will investigate if we can make this opt-in with less bundle size impact, let's see...

@mydea mydea force-pushed the fn/breadcrumbs-fetch-size branch from 37124b1 to afa7415 Compare March 10, 2023 13:42
@mydea
Copy link
Member Author

mydea commented Mar 10, 2023

So I refactored this for a smaller bundle size impact, into a new optional integration. I updated the OP accordingly, IMHO this is also something we can then later reuse for adding e.g. the whole request/response body, if we ever need/want this.

@mydea mydea changed the title feat(util): Add request_body_size & response_body_size to fetch/xhr feat(integrations): Add request_body_size & response_body_size to fetch/xhr Mar 10, 2023
@mydea mydea changed the title feat(integrations): Add request_body_size & response_body_size to fetch/xhr feat(integrations): Add new optional ExtendedNetworkBreadcrumbs integration Mar 10, 2023
scripts/node-unit-tests.ts Outdated Show resolved Hide resolved
Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

LGTM! Had some minor comments but I like the refactor of the types and the idea with the optional integration. I think it's justified to tell replay users to use this integration if they want to monitor their request bodies (which I know we're not doing yet but eventually).

/**
* Register a callback before a breadcrumb is added.
*/
on?(hook: 'beforeBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void;
Copy link
Member

Choose a reason for hiding this comment

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

l: Hmm we already have a top-level callback called beforeBreadcrumb. my overall logaf is low here but do you think we should rename the hook to something like beforeAddBreadcrumb or something similar?

I'd just like to avoid confusion around the callback and the hook if that makes sense. Although, if we're moving into a hooks world, we might eventually even drop the top level callbacks or refactor them to use the hooks internally. So IMO we can also leave it as is

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I had the same thought. I guess beforeAddBreadcrumb makes sense 馃憤

packages/core/src/hub.ts Show resolved Hide resolved
import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('adds a breadcrumb for basic fetch GET request', async ({ getLocalTestPath, page }) => {
Copy link
Member

Choose a reason for hiding this comment

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

l: So here we're basically testing that the response_body_size is set based on the body size rather than the non-existing content-length header, right? Then let's reflect this in the test title.

I know we currently don't have integration tests for the normal breadcrumbs integration. I think we actually might want to add some for it because having a test with what the title suggests is still valuable IMO. Doesn't have to happen in this PR of course!

Copy link
Member Author

Choose a reason for hiding this comment

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

makes sense! 馃憤

import { sentryTest } from '../../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers';

sentryTest('adds a breadcrumb for basic XHR GET request', async ({ getLocalTestPath, page }) => {
Copy link
Member

Choose a reason for hiding this comment

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

l: Same thing here as for the equivalent fetch test


if (!handlerData.endTimestamp) {
if (!startTimestamp || !endTimestamp || !xhr.__sentry_xhr__) {
Copy link
Member

Choose a reason for hiding this comment

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

Makes sense, let's try it this way.

const onreadystatechangeHandler = function (): void {
if (xhr.readyState === 4) {
const onreadystatechangeHandler: () => void = () => {
// For whatever reason, this is not the same instance here as from the outer method
Copy link
Member

Choose a reason for hiding this comment

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

Ooof no idea.. but given that integration tests are passing, I'd say let's just go with it...

Copy link
Member

@billyvg billyvg left a comment

Choose a reason for hiding this comment

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

Is there a way we could automatically opt in Replay users to this integration? I think this is a pretty big barrier for what we should consider "basic" functionality for Replay.

@mydea mydea force-pushed the fn/breadcrumbs-fetch-size branch from efb115c to 8d07e33 Compare March 13, 2023 15:11
@mydea
Copy link
Member Author

mydea commented Mar 13, 2023

LGTM! Had some minor comments but I like the refactor of the types and the idea with the optional integration. I think it's justified to tell replay users to use this integration if they want to monitor their request bodies (which I know we're not doing yet but eventually).

Yes, I also think it is a good separation of concerns to say "If you want any extended network monitoring, just use this integration (which can have settings in the future)", and keep the default experience as slim as possible :)

@mydea mydea force-pushed the fn/breadcrumbs-fetch-size branch 2 times, most recently from db663d1 to 0916dc3 Compare March 15, 2023 11:03
@mydea mydea changed the title feat(integrations): Add new optional ExtendedNetworkBreadcrumbs integration feat(replay): Capture request/response body size for xhr/fetch breadcrumbs Mar 15, 2023
@mydea
Copy link
Member Author

mydea commented Mar 15, 2023

I updated this to be part of replay, it will be automatically applied there.

@@ -4,5 +4,5 @@
{
"extends": "../tsconfig.test.json",

"include": ["./**/*"]
"include": ["./**/*", "../../replay/test/unit/coreHandlers/extendednetworkbreadcrumbs.test.ts"]
Copy link
Member

Choose a reason for hiding this comment

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

is this case sensitive?

@mydea mydea force-pushed the fn/breadcrumbs-fetch-size branch 4 times, most recently from 913064d to c63f22e Compare March 16, 2023 12:38
PR feedback


rename tests & add breadcrumb tests


browser integraiton tests


move functionality to replay


text encoder


ensure we visit url


avoid page goto?


un-flake XHR/fetch tests


revert node unit test changes??


improve replay test


try make less brittle?


skip xhr tests in non-chromium


fix test
@mydea mydea force-pushed the fn/breadcrumbs-fetch-size branch from c63f22e to 9db62c7 Compare March 16, 2023 13:25
@mydea mydea merged commit 20b46a8 into develop Mar 16, 2023
@mydea mydea deleted the fn/breadcrumbs-fetch-size branch March 16, 2023 14:34
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.

Include bytes sent/received in HTTP requests for replays
3 participants