Skip to content

Commit aaabdbd

Browse files
authored
fix: Recursive logging bug with console recording (#1136)
* fix: Recursive logging bug with console recording * Create violet-melons-itch.md
1 parent f6f07e9 commit aaabdbd

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

.changeset/violet-melons-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'rrweb': patch
3+
---
4+
5+
fix: Recursive logging bug with console recording

packages/rrweb/src/plugins/console/record/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function initLogObserver(
111111
logger = loggerType;
112112
}
113113
let logCount = 0;
114+
let inStack = false;
114115
const cancelHandlers: listenerHandler[] = [];
115116
// add listener to thrown errors
116117
if (logOptions.level.includes('error')) {
@@ -188,6 +189,12 @@ function initLogObserver(
188189
(original: (...args: Array<unknown>) => void) => {
189190
return (...args: Array<unknown>) => {
190191
original.apply(this, args);
192+
if (inStack) {
193+
// If we are already in a stack this means something from the following code is calling a console method
194+
// likely a proxy method called from stringify. We don't want to log this as it will cause an infinite loop
195+
return;
196+
}
197+
inStack = true;
191198
try {
192199
const trace = ErrorStackParser.parse(new Error())
193200
.map((stackFrame: StackFrame) => stackFrame.toString())
@@ -214,6 +221,8 @@ function initLogObserver(
214221
}
215222
} catch (error) {
216223
original('rrweb logger error:', error, ...args);
224+
} finally {
225+
inStack = false;
217226
}
218227
};
219228
},

packages/rrweb/test/__snapshots__/integration.test.ts.snap

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4723,6 +4723,186 @@ exports[`record integration tests mutations should work when blocked class is un
47234723
]"
47244724
`;
47254725

4726+
exports[`record integration tests should handle recursive console messages 1`] = `
4727+
"[
4728+
{
4729+
\\"type\\": 0,
4730+
\\"data\\": {}
4731+
},
4732+
{
4733+
\\"type\\": 1,
4734+
\\"data\\": {}
4735+
},
4736+
{
4737+
\\"type\\": 4,
4738+
\\"data\\": {
4739+
\\"href\\": \\"about:blank\\",
4740+
\\"width\\": 1920,
4741+
\\"height\\": 1080
4742+
}
4743+
},
4744+
{
4745+
\\"type\\": 2,
4746+
\\"data\\": {
4747+
\\"node\\": {
4748+
\\"type\\": 0,
4749+
\\"childNodes\\": [
4750+
{
4751+
\\"type\\": 1,
4752+
\\"name\\": \\"html\\",
4753+
\\"publicId\\": \\"\\",
4754+
\\"systemId\\": \\"\\",
4755+
\\"id\\": 2
4756+
},
4757+
{
4758+
\\"type\\": 2,
4759+
\\"tagName\\": \\"html\\",
4760+
\\"attributes\\": {
4761+
\\"lang\\": \\"en\\"
4762+
},
4763+
\\"childNodes\\": [
4764+
{
4765+
\\"type\\": 2,
4766+
\\"tagName\\": \\"head\\",
4767+
\\"attributes\\": {},
4768+
\\"childNodes\\": [
4769+
{
4770+
\\"type\\": 3,
4771+
\\"textContent\\": \\"\\\\n \\",
4772+
\\"id\\": 5
4773+
},
4774+
{
4775+
\\"type\\": 2,
4776+
\\"tagName\\": \\"meta\\",
4777+
\\"attributes\\": {
4778+
\\"charset\\": \\"UTF-8\\"
4779+
},
4780+
\\"childNodes\\": [],
4781+
\\"id\\": 6
4782+
},
4783+
{
4784+
\\"type\\": 3,
4785+
\\"textContent\\": \\"\\\\n \\",
4786+
\\"id\\": 7
4787+
},
4788+
{
4789+
\\"type\\": 2,
4790+
\\"tagName\\": \\"meta\\",
4791+
\\"attributes\\": {
4792+
\\"name\\": \\"viewport\\",
4793+
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
4794+
},
4795+
\\"childNodes\\": [],
4796+
\\"id\\": 8
4797+
},
4798+
{
4799+
\\"type\\": 3,
4800+
\\"textContent\\": \\"\\\\n \\",
4801+
\\"id\\": 9
4802+
},
4803+
{
4804+
\\"type\\": 2,
4805+
\\"tagName\\": \\"meta\\",
4806+
\\"attributes\\": {
4807+
\\"http-equiv\\": \\"X-UA-Compatible\\",
4808+
\\"content\\": \\"ie=edge\\"
4809+
},
4810+
\\"childNodes\\": [],
4811+
\\"id\\": 10
4812+
},
4813+
{
4814+
\\"type\\": 3,
4815+
\\"textContent\\": \\"\\\\n \\",
4816+
\\"id\\": 11
4817+
},
4818+
{
4819+
\\"type\\": 2,
4820+
\\"tagName\\": \\"title\\",
4821+
\\"attributes\\": {},
4822+
\\"childNodes\\": [
4823+
{
4824+
\\"type\\": 3,
4825+
\\"textContent\\": \\"Log record\\",
4826+
\\"id\\": 13
4827+
}
4828+
],
4829+
\\"id\\": 12
4830+
},
4831+
{
4832+
\\"type\\": 3,
4833+
\\"textContent\\": \\"\\\\n \\",
4834+
\\"id\\": 14
4835+
}
4836+
],
4837+
\\"id\\": 4
4838+
},
4839+
{
4840+
\\"type\\": 3,
4841+
\\"textContent\\": \\"\\\\n \\",
4842+
\\"id\\": 15
4843+
},
4844+
{
4845+
\\"type\\": 2,
4846+
\\"tagName\\": \\"body\\",
4847+
\\"attributes\\": {},
4848+
\\"childNodes\\": [
4849+
{
4850+
\\"type\\": 3,
4851+
\\"textContent\\": \\"\\\\n \\",
4852+
\\"id\\": 17
4853+
},
4854+
{
4855+
\\"type\\": 2,
4856+
\\"tagName\\": \\"script\\",
4857+
\\"attributes\\": {},
4858+
\\"childNodes\\": [
4859+
{
4860+
\\"type\\": 3,
4861+
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
4862+
\\"id\\": 19
4863+
}
4864+
],
4865+
\\"id\\": 18
4866+
},
4867+
{
4868+
\\"type\\": 3,
4869+
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
4870+
\\"id\\": 20
4871+
}
4872+
],
4873+
\\"id\\": 16
4874+
}
4875+
],
4876+
\\"id\\": 3
4877+
}
4878+
],
4879+
\\"id\\": 1
4880+
},
4881+
\\"initialOffset\\": {
4882+
\\"left\\": 0,
4883+
\\"top\\": 0
4884+
}
4885+
}
4886+
},
4887+
{
4888+
\\"type\\": 6,
4889+
\\"data\\": {
4890+
\\"plugin\\": \\"rrweb/console@1\\",
4891+
\\"payload\\": {
4892+
\\"level\\": \\"log\\",
4893+
\\"trace\\": [
4894+
\\"__puppeteer_evaluation_script__:20:21\\"
4895+
],
4896+
\\"payload\\": [
4897+
\\"\\\\\\"Proxied object:\\\\\\"\\",
4898+
\\"\\\\\\"[object Object]\\\\\\"\\"
4899+
]
4900+
}
4901+
}
4902+
}
4903+
]"
4904+
`;
4905+
47264906
exports[`record integration tests should mask texts 1`] = `
47274907
"[
47284908
{

packages/rrweb/test/integration.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,53 @@ describe('record integration tests', function (this: ISuite) {
542542
assertSnapshot(snapshots);
543543
});
544544

545+
it('should handle recursive console messages', async () => {
546+
const page: puppeteer.Page = await browser.newPage();
547+
await page.goto('about:blank');
548+
await page.setContent(
549+
getHtml('log.html', {
550+
plugins:
551+
'[rrwebConsoleRecord.getRecordConsolePlugin()]' as unknown as RecordPlugin<unknown>[],
552+
}),
553+
);
554+
555+
await page.evaluate(() => {
556+
// Some frameworks like Vue.js use proxies to implement reactivity.
557+
// This can cause infinite loops when logging objects.
558+
let recursiveTarget = { foo: 'bar', proxied: 'i-am', proxy: null };
559+
let count = 0;
560+
561+
const handler = {
562+
get(target: any, prop: any, ...args: any[]) {
563+
if (prop === 'proxied') {
564+
if (count > 9) {
565+
return;
566+
}
567+
count++; // We don't want out test to get into an infinite loop...
568+
console.warn(
569+
'proxied was accessed so triggering a console.warn',
570+
target,
571+
);
572+
}
573+
return Reflect.get(target, prop, ...args);
574+
},
575+
};
576+
577+
const proxy = new Proxy(recursiveTarget, handler);
578+
recursiveTarget.proxy = proxy;
579+
580+
console.log('Proxied object:', proxy);
581+
});
582+
583+
await waitForRAF(page);
584+
585+
const snapshots = (await page.evaluate(
586+
'window.snapshots',
587+
)) as eventWithTime[];
588+
// The snapshots should containe 1 console log, not multiple.
589+
assertSnapshot(snapshots);
590+
});
591+
545592
it('should nest record iframe', async () => {
546593
const page: puppeteer.Page = await browser.newPage();
547594
await page.goto(`${serverURL}/html`);

0 commit comments

Comments
 (0)