fix: Recursive logging bug with console recording (#1136)
* fix: Recursive logging bug with console recording * Create violet-melons-itch.md
This commit is contained in:
5
.changeset/violet-melons-itch.md
Normal file
5
.changeset/violet-melons-itch.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'rrweb': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Recursive logging bug with console recording
|
||||||
@@ -111,6 +111,7 @@ function initLogObserver(
|
|||||||
logger = loggerType;
|
logger = loggerType;
|
||||||
}
|
}
|
||||||
let logCount = 0;
|
let logCount = 0;
|
||||||
|
let inStack = false;
|
||||||
const cancelHandlers: listenerHandler[] = [];
|
const cancelHandlers: listenerHandler[] = [];
|
||||||
// add listener to thrown errors
|
// add listener to thrown errors
|
||||||
if (logOptions.level.includes('error')) {
|
if (logOptions.level.includes('error')) {
|
||||||
@@ -188,6 +189,12 @@ function initLogObserver(
|
|||||||
(original: (...args: Array<unknown>) => void) => {
|
(original: (...args: Array<unknown>) => void) => {
|
||||||
return (...args: Array<unknown>) => {
|
return (...args: Array<unknown>) => {
|
||||||
original.apply(this, args);
|
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 {
|
try {
|
||||||
const trace = ErrorStackParser.parse(new Error())
|
const trace = ErrorStackParser.parse(new Error())
|
||||||
.map((stackFrame: StackFrame) => stackFrame.toString())
|
.map((stackFrame: StackFrame) => stackFrame.toString())
|
||||||
@@ -214,6 +221,8 @@ function initLogObserver(
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
original('rrweb logger error:', error, ...args);
|
original('rrweb logger error:', error, ...args);
|
||||||
|
} finally {
|
||||||
|
inStack = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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`] = `
|
exports[`record integration tests should mask texts 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -542,6 +542,53 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
assertSnapshot(snapshots);
|
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 () => {
|
it('should nest record iframe', async () => {
|
||||||
const page: puppeteer.Page = await browser.newPage();
|
const page: puppeteer.Page = await browser.newPage();
|
||||||
await page.goto(`${serverURL}/html`);
|
await page.goto(`${serverURL}/html`);
|
||||||
|
|||||||
Reference in New Issue
Block a user