forked from rrweb-io/rrweb
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: enable rrweb to record and replay log messages in console (rrwe…
…b-io#424) * wip: working on rrweb logger * wip: can record and replay some simple log * wip: can record and replay log's stack * wip: try to serialize object * wip: record and replay console logger hijack all of the console functions. add listener to thrown errors * wip: record and replay console logger add limit to the max number of log records * feat: enable rrweb to record and replay log messages in console this is the implementation of new feature request(issue rrweb-io#234) here are a few points of description. 1. users need to set recordLog option in rrweb.record's parameter to record log messages. The log recorder is off by default. 2. support recording and replaying all kinds of console functions. But the reliability of them should be tested more 3. the stringify function in stringify.ts needs improvement. e.g. robustness, handler for cyclical structures and better support for more kinds of object 4. we can replay the log messages in a simulated html console like LogRocket by implementing the interface "ReplayLogger" in the future * improve: the stringify function 1. handle cyclical structures 2. add stringify option to limit the length of result 3. handle function type * refactor: simplify the type definition of ReplayLogger
- Loading branch information
1 parent
e3beeb4
commit 4e7146e
Showing
13 changed files
with
1,289 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/** | ||
* this file is used to serialize log message to string | ||
* | ||
*/ | ||
|
||
import { StringifyOptions } from '../types'; | ||
|
||
/** | ||
* transfer the node path in Event to string | ||
* @param node the first node in a node path array | ||
*/ | ||
function pathToSelector(node: HTMLElement): string | '' { | ||
if (!node || !node.outerHTML) { | ||
return ''; | ||
} | ||
|
||
var path = ''; | ||
while (node.parentElement) { | ||
var name = node.localName; | ||
if (!name) break; | ||
name = name.toLowerCase(); | ||
var parent = node.parentElement; | ||
|
||
var domSiblings = []; | ||
|
||
if (parent.children && parent.children.length > 0) { | ||
for (var i = 0; i < parent.children.length; i++) { | ||
var sibling = parent.children[i]; | ||
if (sibling.localName && sibling.localName.toLowerCase) { | ||
if (sibling.localName.toLowerCase() === name) { | ||
domSiblings.push(sibling); | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (domSiblings.length > 1) { | ||
name += ':eq(' + domSiblings.indexOf(node) + ')'; | ||
} | ||
path = name + (path ? '>' + path : ''); | ||
node = parent; | ||
} | ||
|
||
return path; | ||
} | ||
|
||
/** | ||
* stringify any js object | ||
* @param obj the object to stringify | ||
*/ | ||
export function stringify( | ||
obj: any, | ||
stringifyOptions?: StringifyOptions, | ||
): string { | ||
const options: StringifyOptions = { | ||
numOfKeysLimit: 50, | ||
}; | ||
Object.assign(options, stringifyOptions); | ||
let stack: any[] = [], | ||
keys: any[] = []; | ||
return JSON.stringify(obj, function (key, value) { | ||
/** | ||
* forked from https://github.com/moll/json-stringify-safe/blob/master/stringify.js | ||
* to deCycle the object | ||
*/ | ||
if (stack.length > 0) { | ||
var thisPos = stack.indexOf(this); | ||
~thisPos ? stack.splice(thisPos + 1) : stack.push(this); | ||
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); | ||
if (~stack.indexOf(value)) { | ||
if (stack[0] === value) value = '[Circular ~]'; | ||
else | ||
value = | ||
'[Circular ~.' + | ||
keys.slice(0, stack.indexOf(value)).join('.') + | ||
']'; | ||
} | ||
} else stack.push(value); | ||
/* END of the FORK */ | ||
|
||
if (value === null || value === undefined) return value; | ||
if (shouldToString(value)) { | ||
return toString(value); | ||
} | ||
if (value instanceof Event) { | ||
const eventResult: any = {}; | ||
for (const key in value) { | ||
const eventValue = (value as any)[key]; | ||
if (Array.isArray(eventValue)) | ||
eventResult[key] = pathToSelector( | ||
eventValue.length ? eventValue[0] : null, | ||
); | ||
else eventResult[key] = eventValue; | ||
} | ||
return eventResult; | ||
} else if (value instanceof Node) { | ||
if (value instanceof HTMLElement) return value ? value.outerHTML : ''; | ||
return value.nodeName; | ||
} | ||
return value; | ||
}); | ||
|
||
/** | ||
* whether we should call toString function of this object | ||
*/ | ||
function shouldToString(obj: object): boolean { | ||
if ( | ||
typeof obj === 'object' && | ||
Object.keys(obj).length > options.numOfKeysLimit | ||
) | ||
return true; | ||
if (typeof obj === 'function') return true; | ||
return false; | ||
} | ||
|
||
/** | ||
* limit the toString() result according to option | ||
*/ | ||
function toString(obj: object): string { | ||
let str = obj.toString(); | ||
if (options.stringLengthLimit && str.length > options.stringLengthLimit) { | ||
str = `${str.slice(0, options.stringLengthLimit)}...`; | ||
} | ||
return str; | ||
} | ||
} |
Oops, something went wrong.