Skip to content

Commit

Permalink
fix: Recursive logging bug with console recording (#1136)
Browse files Browse the repository at this point in the history
* fix: Recursive logging bug with console recording

* Create violet-melons-itch.md
  • Loading branch information
benjackwhite committed Feb 16, 2023
1 parent f6f07e9 commit aaabdbd
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-melons-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rrweb': patch
---

fix: Recursive logging bug with console recording
9 changes: 9 additions & 0 deletions packages/rrweb/src/plugins/console/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ function initLogObserver(
logger = loggerType;
}
let logCount = 0;
let inStack = false;
const cancelHandlers: listenerHandler[] = [];
// add listener to thrown errors
if (logOptions.level.includes('error')) {
Expand Down Expand Up @@ -188,6 +189,12 @@ function initLogObserver(
(original: (...args: Array<unknown>) => void) => {
return (...args: Array<unknown>) => {
original.apply(this, args);
if (inStack) {
// If we are already in a stack this means something from the following code is calling a console method
// likely a proxy method called from stringify. We don't want to log this as it will cause an infinite loop
return;
}
inStack = true;
try {
const trace = ErrorStackParser.parse(new Error())
.map((stackFrame: StackFrame) => stackFrame.toString())
Expand All @@ -214,6 +221,8 @@ function initLogObserver(
}
} catch (error) {
original('rrweb logger error:', error, ...args);
} finally {
inStack = false;
}
};
},
Expand Down
180 changes: 180 additions & 0 deletions packages/rrweb/test/__snapshots__/integration.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4723,6 +4723,186 @@ exports[`record integration tests mutations should work when blocked class is un
]"
`;

exports[`record integration tests should handle recursive console messages 1`] = `
"[
{
\\"type\\": 0,
\\"data\\": {}
},
{
\\"type\\": 1,
\\"data\\": {}
},
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {
\\"lang\\": \\"en\\"
},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 5
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"charset\\": \\"UTF-8\\"
},
\\"childNodes\\": [],
\\"id\\": 6
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 7
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"name\\": \\"viewport\\",
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
},
\\"childNodes\\": [],
\\"id\\": 8
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
},
{
\\"type\\": 2,
\\"tagName\\": \\"meta\\",
\\"attributes\\": {
\\"http-equiv\\": \\"X-UA-Compatible\\",
\\"content\\": \\"ie=edge\\"
},
\\"childNodes\\": [],
\\"id\\": 10
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 11
},
{
\\"type\\": 2,
\\"tagName\\": \\"title\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"Log record\\",
\\"id\\": 13
}
],
\\"id\\": 12
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 14
}
],
\\"id\\": 4
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 15
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 17
},
{
\\"type\\": 2,
\\"tagName\\": \\"script\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
\\"id\\": 19
}
],
\\"id\\": 18
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
\\"id\\": 20
}
],
\\"id\\": 16
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 6,
\\"data\\": {
\\"plugin\\": \\"rrweb/console@1\\",
\\"payload\\": {
\\"level\\": \\"log\\",
\\"trace\\": [
\\"__puppeteer_evaluation_script__:20:21\\"
],
\\"payload\\": [
\\"\\\\\\"Proxied object:\\\\\\"\\",
\\"\\\\\\"[object Object]\\\\\\"\\"
]
}
}
}
]"
`;

exports[`record integration tests should mask texts 1`] = `
"[
{
Expand Down
47 changes: 47 additions & 0 deletions packages/rrweb/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,53 @@ describe('record integration tests', function (this: ISuite) {
assertSnapshot(snapshots);
});

it('should handle recursive console messages', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');
await page.setContent(
getHtml('log.html', {
plugins:
'[rrwebConsoleRecord.getRecordConsolePlugin()]' as unknown as RecordPlugin<unknown>[],
}),
);

await page.evaluate(() => {
// Some frameworks like Vue.js use proxies to implement reactivity.
// This can cause infinite loops when logging objects.
let recursiveTarget = { foo: 'bar', proxied: 'i-am', proxy: null };
let count = 0;

const handler = {
get(target: any, prop: any, ...args: any[]) {
if (prop === 'proxied') {
if (count > 9) {
return;
}
count++; // We don't want out test to get into an infinite loop...
console.warn(
'proxied was accessed so triggering a console.warn',
target,
);
}
return Reflect.get(target, prop, ...args);
},
};

const proxy = new Proxy(recursiveTarget, handler);
recursiveTarget.proxy = proxy;

console.log('Proxied object:', proxy);
});

await waitForRAF(page);

const snapshots = (await page.evaluate(
'window.snapshots',
)) as eventWithTime[];
// The snapshots should containe 1 console log, not multiple.
assertSnapshot(snapshots);
});

it('should nest record iframe', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto(`${serverURL}/html`);
Expand Down

0 comments on commit aaabdbd

Please sign in to comment.