Skip to content

Commit 654d5f7

Browse files
committed
ref(replay): Add BODY_PARSE_ERROR warning when network body fails
1 parent 1036537 commit 654d5f7

File tree

4 files changed

+91
-22
lines changed

4 files changed

+91
-22
lines changed

packages/replay/src/coreHandlers/util/fetchUtils.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { logger } from '@sentry/utils';
33

44
import type {
55
FetchHint,
6+
NetworkMetaWarning,
67
ReplayContainer,
78
ReplayNetworkOptions,
89
ReplayNetworkRequestData,
@@ -16,6 +17,7 @@ import {
1617
getBodySize,
1718
getBodyString,
1819
makeNetworkReplayBreadcrumb,
20+
mergeWarningsIntoMeta,
1921
parseContentLengthHeader,
2022
urlMatches,
2123
} from './networkUtils';
@@ -118,8 +120,14 @@ function _getRequestInfo(
118120

119121
// We only want to transmit string or string-like bodies
120122
const requestBody = _getFetchRequestArgBody(input);
121-
const bodyStr = getBodyString(requestBody);
122-
return buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
123+
const [bodyStr, warning] = getBodyString(requestBody);
124+
const data = buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
125+
126+
if (data && warning) {
127+
data._meta = mergeWarningsIntoMeta(data._meta, [warning]);
128+
}
129+
130+
return data;
123131
}
124132

125133
async function _getResponseInfo(
@@ -145,11 +153,40 @@ async function _getResponseInfo(
145153
}
146154

147155
// Only clone the response if we need to
148-
try {
149-
// We have to clone this, as the body can only be read once
150-
const res = response.clone();
151-
const bodyText = await _parseFetchBody(res);
152156

157+
const [bodyText, warning] = await _parseFetchResponseBody(response);
158+
const result = getResponseData(bodyText, {
159+
networkCaptureBodies,
160+
textEncoder,
161+
responseBodySize,
162+
captureDetails,
163+
headers,
164+
});
165+
166+
if (result && warning) {
167+
result._meta = mergeWarningsIntoMeta(result._meta, [warning]);
168+
}
169+
170+
return result;
171+
}
172+
173+
function getResponseData(
174+
bodyText: string | undefined,
175+
{
176+
networkCaptureBodies,
177+
textEncoder,
178+
responseBodySize,
179+
captureDetails,
180+
headers,
181+
}: {
182+
captureDetails: boolean;
183+
networkCaptureBodies: boolean;
184+
responseBodySize: number | undefined;
185+
headers: Record<string, string>;
186+
textEncoder: TextEncoderInternal;
187+
},
188+
): ReplayNetworkRequestOrResponse | undefined {
189+
try {
153190
const size =
154191
bodyText && bodyText.length && responseBodySize === undefined
155192
? getBodySize(bodyText, textEncoder)
@@ -171,11 +208,15 @@ async function _getResponseInfo(
171208
}
172209
}
173210

174-
async function _parseFetchBody(response: Response): Promise<string | undefined> {
211+
async function _parseFetchResponseBody(response: Response): Promise<[string | undefined, NetworkMetaWarning?]> {
175212
try {
176-
return await response.text();
177-
} catch {
178-
return undefined;
213+
// We have to clone this, as the body can only be read once
214+
const res = response.clone();
215+
const text = await res.text();
216+
return [text];
217+
} catch (errorr) {
218+
__DEBUG_BUILD__ && logger.warn('[Replay] Failed to read response body', errorr);
219+
return [undefined, 'BODY_PARSE_ERROR'];
179220
}
180221
}
181222

packages/replay/src/coreHandlers/util/networkUtils.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,39 @@ export function parseContentLengthHeader(header: string | null | undefined): num
6161
}
6262

6363
/** Get the string representation of a body. */
64-
export function getBodyString(body: unknown): string | undefined {
64+
export function getBodyString(body: unknown): [string | undefined, NetworkMetaWarning?] {
6565
try {
6666
if (typeof body === 'string') {
67-
return body;
67+
return [body];
6868
}
6969

7070
if (body instanceof URLSearchParams) {
71-
return body.toString();
71+
return [body.toString()];
7272
}
7373

7474
if (body instanceof FormData) {
75-
return _serializeFormData(body);
75+
return [_serializeFormData(body)];
7676
}
7777
} catch {
7878
__DEBUG_BUILD__ && logger.warn('[Replay] Failed to serialize body', body);
79+
return [undefined, 'BODY_PARSE_ERROR'];
7980
}
8081

8182
__DEBUG_BUILD__ && logger.info('[Replay] Skipping network body because of body type', body);
8283

83-
return undefined;
84+
return [undefined];
85+
}
86+
87+
/** Merge warnings into an possibly existing meta. */
88+
export function mergeWarningsIntoMeta(
89+
meta: ReplayNetworkRequestOrResponse['_meta'],
90+
warnings: NetworkMetaWarning[],
91+
): ReplayNetworkRequestOrResponse['_meta'] {
92+
const newMeta = { ...meta };
93+
const existingWarnings = newMeta.warnings || [];
94+
newMeta.warnings = [...existingWarnings, ...warnings];
95+
96+
return newMeta;
8497
}
8598

8699
/** Convert ReplayNetworkRequestData to a PerformanceEntry. */

packages/replay/src/coreHandlers/util/xhrUtils.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import type { Breadcrumb, TextEncoderInternal, XhrBreadcrumbData } from '@sentry/types';
22
import { logger, SENTRY_XHR_DATA_KEY } from '@sentry/utils';
33

4-
import type { ReplayContainer, ReplayNetworkOptions, ReplayNetworkRequestData, XhrHint } from '../../types';
4+
import type {
5+
NetworkMetaWarning,
6+
ReplayContainer,
7+
ReplayNetworkOptions,
8+
ReplayNetworkRequestData,
9+
XhrHint,
10+
} from '../../types';
511
import { addNetworkBreadcrumb } from './addNetworkBreadcrumb';
612
import {
713
buildNetworkRequestOrResponse,
@@ -10,6 +16,7 @@ import {
1016
getBodySize,
1117
getBodyString,
1218
makeNetworkReplayBreadcrumb,
19+
mergeWarningsIntoMeta,
1320
parseContentLengthHeader,
1421
urlMatches,
1522
} from './networkUtils';
@@ -103,12 +110,20 @@ function _prepareXhrData(
103110
: {};
104111
const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
105112

106-
const requestBody = options.networkCaptureBodies ? getBodyString(input) : undefined;
107-
const responseBody = options.networkCaptureBodies ? _getXhrResponseBody(xhr) : undefined;
113+
const [requestBody, requestWarning] = options.networkCaptureBodies ? getBodyString(input) : [undefined];
114+
const [responseBody, responseWarning] = options.networkCaptureBodies ? _getXhrResponseBody(xhr) : [undefined];
108115

109116
const request = buildNetworkRequestOrResponse(networkRequestHeaders, requestBodySize, requestBody);
110117
const response = buildNetworkRequestOrResponse(networkResponseHeaders, responseBodySize, responseBody);
111118

119+
if (request && requestWarning) {
120+
request._meta = mergeWarningsIntoMeta(request._meta, [requestWarning]);
121+
}
122+
123+
if (response && responseWarning) {
124+
response._meta = mergeWarningsIntoMeta(response._meta, [responseWarning]);
125+
}
126+
112127
return {
113128
startTimestamp,
114129
endTimestamp,
@@ -134,12 +149,12 @@ function getResponseHeaders(xhr: XMLHttpRequest): Record<string, string> {
134149
}, {});
135150
}
136151

137-
function _getXhrResponseBody(xhr: XMLHttpRequest): string | undefined {
152+
function _getXhrResponseBody(xhr: XMLHttpRequest): [string | undefined, NetworkMetaWarning?] {
138153
// We collect errors that happen, but only log them if we can't get any response body
139154
const errors: unknown[] = [];
140155

141156
try {
142-
return xhr.responseText;
157+
return [xhr.responseText];
143158
} catch (e) {
144159
errors.push(e);
145160
}
@@ -154,5 +169,5 @@ function _getXhrResponseBody(xhr: XMLHttpRequest): string | undefined {
154169

155170
__DEBUG_BUILD__ && logger.warn('[Replay] Failed to get xhr response body', ...errors);
156171

157-
return undefined;
172+
return [undefined];
158173
}

packages/replay/src/types/request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ type JsonArray = unknown[];
33

44
export type NetworkBody = JsonObject | JsonArray | string;
55

6-
export type NetworkMetaWarning = 'MAYBE_JSON_TRUNCATED' | 'TEXT_TRUNCATED' | 'URL_SKIPPED';
6+
export type NetworkMetaWarning = 'MAYBE_JSON_TRUNCATED' | 'TEXT_TRUNCATED' | 'URL_SKIPPED' | 'BODY_PARSE_ERROR';
77

88
interface NetworkMeta {
99
warnings?: NetworkMetaWarning[];

0 commit comments

Comments
 (0)