Skip to content

Commit 454a756

Browse files
committed
fix(replay): Ensure console breadcrumb args are truncated
1 parent 100369e commit 454a756

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

packages/replay/src/coreHandlers/handleScope.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import type { Breadcrumb, Scope } from '@sentry/types';
2+
import { normalize } from '@sentry/utils';
23

34
import type { ReplayContainer } from '../types';
45
import { createBreadcrumb } from '../util/createBreadcrumb';
6+
import { fixJson } from '../util/truncateJson/fixJson';
57
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';
68

79
let _LAST_BREADCRUMB: null | Breadcrumb = null;
810

11+
export const CONSOLE_ARG_MAX_SIZE = 5_000;
12+
913
export const handleScopeListener: (replay: ReplayContainer) => (scope: Scope) => void =
1014
(replay: ReplayContainer) =>
1115
(scope: Scope): void => {
@@ -48,5 +52,60 @@ export function handleScope(scope: Scope): Breadcrumb | null {
4852
return null;
4953
}
5054

55+
if (newBreadcrumb.category === 'console') {
56+
return normalizeConsoleBreadcrumb(newBreadcrumb);
57+
}
58+
5159
return createBreadcrumb(newBreadcrumb);
5260
}
61+
62+
/** exported for tests only */
63+
export function normalizeConsoleBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb {
64+
const args = breadcrumb.data && breadcrumb.data.arguments;
65+
66+
if (!Array.isArray(args) || args.length === 0) {
67+
return createBreadcrumb(breadcrumb);
68+
}
69+
70+
let isTruncated = false;
71+
72+
// Avoid giant args captures
73+
const normalizedArgs = args.map(arg => {
74+
if (!arg) {
75+
return arg;
76+
}
77+
if (typeof arg === 'string') {
78+
if (arg.length > CONSOLE_ARG_MAX_SIZE) {
79+
isTruncated = true;
80+
return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
81+
}
82+
83+
return arg;
84+
}
85+
if (typeof arg === 'object') {
86+
try {
87+
const normalizedArg = normalize(arg, 7);
88+
const stringified = JSON.stringify(normalizedArg);
89+
if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
90+
isTruncated = true;
91+
const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
92+
return JSON.parse(fixedJson);
93+
}
94+
return normalizedArg;
95+
} catch {
96+
// fall back to default
97+
}
98+
}
99+
100+
return arg;
101+
});
102+
103+
return createBreadcrumb({
104+
...breadcrumb,
105+
data: {
106+
...breadcrumb.data,
107+
arguments: normalizedArgs,
108+
_meta: isTruncated ? { warnings: ['CONSOLE_ARG_TRUNCATED'] } : undefined,
109+
},
110+
});
111+
}

packages/replay/test/unit/coreHandlers/handleScope.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,91 @@ describe('Unit | coreHandlers | handleScope', () => {
5959
expect(mockHandleScope).toHaveBeenCalledTimes(1);
6060
expect(mockHandleScope).toHaveReturnedWith(null);
6161
});
62+
63+
describe('normalizeConsoleBreadcrumb', () => {
64+
it('handles console messages with no arguments', () => {
65+
const breadcrumb: Breadcrumb = { category: 'console', message: 'test' };
66+
const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb);
67+
68+
expect(actual).toMatchObject({ category: 'console', message: 'test' });
69+
});
70+
71+
it('handles console messages with empty arguments', () => {
72+
const breadcrumb: Breadcrumb = { category: 'console', message: 'test', data: { arguments: [] } };
73+
const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb);
74+
75+
expect(actual).toMatchObject({ category: 'console', message: 'test', data: { arguments: [] } });
76+
});
77+
78+
it('handles console messages with simple arguments', () => {
79+
const breadcrumb: Breadcrumb = {
80+
category: 'console',
81+
message: 'test',
82+
data: { arguments: [1, 'a', true, null, undefined] },
83+
};
84+
const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb);
85+
86+
expect(actual).toMatchObject({
87+
category: 'console',
88+
message: 'test',
89+
data: {
90+
arguments: [1, 'a', true, null, undefined],
91+
},
92+
});
93+
});
94+
95+
it('truncates large strings', () => {
96+
const breadcrumb: Breadcrumb = {
97+
category: 'console',
98+
message: 'test',
99+
data: {
100+
arguments: [
101+
'a'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE + 10),
102+
'b'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE + 10),
103+
],
104+
},
105+
};
106+
const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb);
107+
108+
expect(actual).toMatchObject({
109+
category: 'console',
110+
message: 'test',
111+
data: {
112+
arguments: [
113+
`${'a'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE)}…`,
114+
`${'b'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE)}…`,
115+
],
116+
_meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] },
117+
},
118+
});
119+
});
120+
121+
it('truncates large JSON objects', () => {
122+
const breadcrumb: Breadcrumb = {
123+
category: 'console',
124+
message: 'test',
125+
data: {
126+
arguments: [
127+
{ aa: 'yes' },
128+
{ bb: 'b'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE + 10) },
129+
{ c: 'c'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE + 10) },
130+
],
131+
},
132+
};
133+
const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb);
134+
135+
expect(actual).toMatchObject({
136+
category: 'console',
137+
message: 'test',
138+
data: {
139+
arguments: [
140+
{ aa: 'yes' },
141+
{ bb: `${'b'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE - 7)}~~` },
142+
{ c: `${'c'.repeat(HandleScope.CONSOLE_ARG_MAX_SIZE - 6)}~~` },
143+
],
144+
_meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] },
145+
},
146+
});
147+
});
148+
});
62149
});

0 commit comments

Comments
 (0)