feat: enable rrweb to record and replay log messages in console (#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 #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
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
recordOptions,
|
||||
IncrementalSource,
|
||||
listenerHandler,
|
||||
LogRecordOptions,
|
||||
} from '../types';
|
||||
|
||||
function wrapEvent(e: event): eventWithTime {
|
||||
@@ -46,6 +47,7 @@ function record<T = eventWithTime>(
|
||||
mousemoveWait,
|
||||
recordCanvas = false,
|
||||
collectFonts = false,
|
||||
recordLog = false,
|
||||
} = options;
|
||||
// runtime checks for user options
|
||||
if (!emit) {
|
||||
@@ -98,6 +100,37 @@ function record<T = eventWithTime>(
|
||||
: _slimDOMOptions
|
||||
? _slimDOMOptions
|
||||
: {};
|
||||
const defaultLogOptions: LogRecordOptions = {
|
||||
level: [
|
||||
'assert',
|
||||
'clear',
|
||||
'count',
|
||||
'countReset',
|
||||
'debug',
|
||||
'dir',
|
||||
'dirxml',
|
||||
'error',
|
||||
'group',
|
||||
'groupCollapsed',
|
||||
'groupEnd',
|
||||
'info',
|
||||
'log',
|
||||
'table',
|
||||
'time',
|
||||
'timeEnd',
|
||||
'timeLog',
|
||||
'trace',
|
||||
'warn',
|
||||
],
|
||||
lengthThreshold: 1000,
|
||||
logger: console,
|
||||
};
|
||||
|
||||
const logOptions: LogRecordOptions = recordLog
|
||||
? recordLog === true
|
||||
? defaultLogOptions
|
||||
: Object.assign({}, defaultLogOptions, recordLog)
|
||||
: {};
|
||||
|
||||
polyfill();
|
||||
|
||||
@@ -312,6 +345,16 @@ function record<T = eventWithTime>(
|
||||
},
|
||||
}),
|
||||
),
|
||||
logCb: (p) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.IncrementalSnapshot,
|
||||
data: {
|
||||
source: IncrementalSource.Log,
|
||||
...p,
|
||||
},
|
||||
}),
|
||||
),
|
||||
blockClass,
|
||||
blockSelector,
|
||||
ignoreClass,
|
||||
@@ -322,6 +365,7 @@ function record<T = eventWithTime>(
|
||||
recordCanvas,
|
||||
collectFonts,
|
||||
slimDOMOptions,
|
||||
logOptions,
|
||||
},
|
||||
hooks,
|
||||
),
|
||||
|
||||
@@ -36,8 +36,13 @@ import {
|
||||
fontCallback,
|
||||
fontParam,
|
||||
MaskInputFn,
|
||||
logCallback,
|
||||
LogRecordOptions,
|
||||
Logger,
|
||||
LogLevel,
|
||||
} from '../types';
|
||||
import MutationBuffer from './mutation';
|
||||
import { stringify } from './stringify';
|
||||
|
||||
export const mutationBuffer = new MutationBuffer();
|
||||
|
||||
@@ -499,6 +504,100 @@ function initFontObserver(cb: fontCallback): listenerHandler {
|
||||
};
|
||||
}
|
||||
|
||||
function initLogObserver(
|
||||
cb: logCallback,
|
||||
logOptions: LogRecordOptions,
|
||||
): listenerHandler {
|
||||
const logger = logOptions.logger;
|
||||
if (!logger) return () => {};
|
||||
let logCount = 0;
|
||||
const cancelHandlers: any[] = [];
|
||||
// add listener to thrown errors
|
||||
if (logOptions.level!.includes('error')) {
|
||||
if (window) {
|
||||
const originalOnError = window.onerror;
|
||||
window.onerror = (...args: any[]) => {
|
||||
originalOnError && originalOnError.apply(this, args);
|
||||
let stack: Array<string> = [];
|
||||
if (args[args.length - 1] instanceof Error)
|
||||
// 0(the second parameter) tells parseStack that every stack in Error is useful
|
||||
stack = parseStack(args[args.length - 1].stack, 0);
|
||||
const payload = [stringify(args[0], logOptions.stringifyOptions)];
|
||||
cb({
|
||||
level: 'error',
|
||||
trace: stack,
|
||||
payload: payload,
|
||||
});
|
||||
};
|
||||
cancelHandlers.push(() => {
|
||||
window.onerror = originalOnError;
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const levelType of logOptions.level!)
|
||||
cancelHandlers.push(replace(logger, levelType));
|
||||
return () => {
|
||||
cancelHandlers.forEach((h) => h());
|
||||
};
|
||||
|
||||
/**
|
||||
* replace the original console function and record logs
|
||||
* @param logger the logger object such as Console
|
||||
* @param level the name of log function to be replaced
|
||||
*/
|
||||
function replace(logger: Logger, level: LogLevel) {
|
||||
if (!logger[level]) return () => {};
|
||||
// replace the logger.{level}. return a restore function
|
||||
return patch(logger, level, (original) => {
|
||||
return (...args: any[]) => {
|
||||
original.apply(this, args);
|
||||
try {
|
||||
const stack = parseStack(new Error().stack);
|
||||
const payload = args.map((s) =>
|
||||
stringify(s, logOptions.stringifyOptions),
|
||||
);
|
||||
logCount++;
|
||||
if (logCount < logOptions.lengthThreshold!)
|
||||
cb({
|
||||
level: level,
|
||||
trace: stack,
|
||||
payload: payload,
|
||||
});
|
||||
else if (logCount === logOptions.lengthThreshold)
|
||||
// notify the user
|
||||
cb({
|
||||
level: 'warn',
|
||||
trace: [],
|
||||
payload: [
|
||||
stringify('The number of log records reached the threshold.'),
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
original('rrweb logger error:', error, ...args);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
/**
|
||||
* parse single stack message to an stack array.
|
||||
* @param stack the stack message to be parsed
|
||||
* @param omitDepth omit specific depth of useless stack. omit hijacked log function by default
|
||||
*/
|
||||
function parseStack(
|
||||
stack: string | undefined,
|
||||
omitDepth: number = 1,
|
||||
): Array<string> {
|
||||
let stacks: string[] = [];
|
||||
if (stack) {
|
||||
stacks = stack
|
||||
.split('at')
|
||||
.splice(1 + omitDepth)
|
||||
.map((s) => s.trim());
|
||||
}
|
||||
return stacks;
|
||||
}
|
||||
}
|
||||
|
||||
function mergeHooks(o: observerParam, hooks: hooksParam) {
|
||||
const {
|
||||
mutationCb,
|
||||
@@ -511,6 +610,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
||||
styleSheetRuleCb,
|
||||
canvasMutationCb,
|
||||
fontCb,
|
||||
logCb,
|
||||
} = o;
|
||||
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
|
||||
if (hooks.mutation) {
|
||||
@@ -572,6 +672,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
||||
}
|
||||
fontCb(...p);
|
||||
};
|
||||
o.logCb = (...p: Arguments<logCallback>) => {
|
||||
if (hooks.log) {
|
||||
hooks.log(...p);
|
||||
}
|
||||
logCb(...p);
|
||||
};
|
||||
}
|
||||
|
||||
export function initObservers(
|
||||
@@ -617,6 +723,9 @@ export function initObservers(
|
||||
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass)
|
||||
: () => {};
|
||||
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
|
||||
const logObserver = o.logOptions
|
||||
? initLogObserver(o.logCb, o.logOptions)
|
||||
: () => {};
|
||||
|
||||
return () => {
|
||||
mutationObserver.disconnect();
|
||||
@@ -629,5 +738,6 @@ export function initObservers(
|
||||
styleSheetObserver();
|
||||
canvasMutationObserver();
|
||||
fontObserver();
|
||||
logObserver();
|
||||
};
|
||||
}
|
||||
|
||||
126
src/record/stringify.ts
Normal file
126
src/record/stringify.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import {
|
||||
inputData,
|
||||
canvasMutationData,
|
||||
ElementState,
|
||||
LogReplayConfig,
|
||||
logData,
|
||||
ReplayLogger,
|
||||
} from '../types';
|
||||
import {
|
||||
mirror,
|
||||
@@ -54,6 +57,31 @@ const defaultMouseTailConfig = {
|
||||
strokeStyle: 'red',
|
||||
} as const;
|
||||
|
||||
const defaultLogConfig: LogReplayConfig = {
|
||||
level: [
|
||||
'assert',
|
||||
'clear',
|
||||
'count',
|
||||
'countReset',
|
||||
'debug',
|
||||
'dir',
|
||||
'dirxml',
|
||||
'error',
|
||||
'group',
|
||||
'groupCollapsed',
|
||||
'groupEnd',
|
||||
'info',
|
||||
'log',
|
||||
'table',
|
||||
'time',
|
||||
'timeEnd',
|
||||
'timeLog',
|
||||
'trace',
|
||||
'warn',
|
||||
],
|
||||
replayLogger: undefined,
|
||||
};
|
||||
|
||||
export class Replayer {
|
||||
public wrapper: HTMLDivElement;
|
||||
public iframe: HTMLIFrameElement;
|
||||
@@ -104,8 +132,11 @@ export class Replayer {
|
||||
UNSAFE_replayCanvas: false,
|
||||
pauseAnimation: true,
|
||||
mouseTail: defaultMouseTailConfig,
|
||||
logConfig: defaultLogConfig,
|
||||
};
|
||||
this.config = Object.assign({}, defaultConfig, config);
|
||||
if (!this.config.logConfig.replayLogger)
|
||||
this.config.logConfig.replayLogger = this.getConsoleLogger();
|
||||
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.getCastFn = this.getCastFn.bind(this);
|
||||
@@ -904,6 +935,18 @@ export class Replayer {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IncrementalSource.Log: {
|
||||
try {
|
||||
const logData = e.data as logData;
|
||||
const replayLogger = this.config.logConfig.replayLogger!;
|
||||
if (typeof replayLogger[logData.level] === 'function')
|
||||
replayLogger[logData.level]!(logData);
|
||||
} catch (error) {
|
||||
if (this.config.showWarning) {
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -1159,6 +1202,48 @@ export class Replayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* format the trace data to a string
|
||||
* @param data the log data
|
||||
*/
|
||||
private formatMessage(data: logData): string {
|
||||
if (data.trace.length === 0) return '';
|
||||
const stackPrefix = '\n\tat ';
|
||||
let result = stackPrefix;
|
||||
result += data.trace.join(stackPrefix);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* generate a console log replayer which implement the interface ReplayLogger
|
||||
*/
|
||||
private getConsoleLogger(): ReplayLogger {
|
||||
const rrwebOriginal = '__rrweb_original__';
|
||||
const replayLogger: ReplayLogger = {};
|
||||
for (const level of this.config.logConfig.level!)
|
||||
if (level === 'trace')
|
||||
replayLogger[level] = (data: logData) => {
|
||||
const logger = (console.log as any)[rrwebOriginal]
|
||||
? (console.log as any)[rrwebOriginal]
|
||||
: console.log;
|
||||
logger(
|
||||
...data.payload.map((s) => JSON.parse(s)),
|
||||
this.formatMessage(data),
|
||||
);
|
||||
};
|
||||
else
|
||||
replayLogger[level] = (data: logData) => {
|
||||
const logger = (console[level] as any)[rrwebOriginal]
|
||||
? (console[level] as any)[rrwebOriginal]
|
||||
: console[level];
|
||||
logger(
|
||||
...data.payload.map((s) => JSON.parse(s)),
|
||||
this.formatMessage(data),
|
||||
);
|
||||
};
|
||||
return replayLogger;
|
||||
}
|
||||
|
||||
private legacy_resolveMissingNode(
|
||||
map: missingNodeMap,
|
||||
parent: Node,
|
||||
|
||||
@@ -165,6 +165,7 @@ export function createPlayerService(
|
||||
};
|
||||
}),
|
||||
play(ctx) {
|
||||
console.warn('play');
|
||||
const { timer, events, baselineTime, lastPlayedEvent } = ctx;
|
||||
timer.clear();
|
||||
for (const event of events) {
|
||||
|
||||
100
src/types.ts
100
src/types.ts
@@ -52,6 +52,11 @@ export type metaEvent = {
|
||||
};
|
||||
};
|
||||
|
||||
export type logEvent = {
|
||||
type: EventType.IncrementalSnapshot;
|
||||
data: incrementalData;
|
||||
};
|
||||
|
||||
export type customEvent<T = unknown> = {
|
||||
type: EventType.Custom;
|
||||
data: {
|
||||
@@ -74,6 +79,7 @@ export enum IncrementalSource {
|
||||
StyleSheetRule,
|
||||
CanvasMutation,
|
||||
Font,
|
||||
Log,
|
||||
}
|
||||
|
||||
export type mutationData = {
|
||||
@@ -118,6 +124,10 @@ export type fontData = {
|
||||
source: IncrementalSource.Font;
|
||||
} & fontParam;
|
||||
|
||||
export type logData = {
|
||||
source: IncrementalSource.Log;
|
||||
} & LogParam;
|
||||
|
||||
export type incrementalData =
|
||||
| mutationData
|
||||
| mousemoveData
|
||||
@@ -128,7 +138,8 @@ export type incrementalData =
|
||||
| mediaInteractionData
|
||||
| styleSheetRuleData
|
||||
| canvasMutationData
|
||||
| fontData;
|
||||
| fontData
|
||||
| logData;
|
||||
|
||||
export type event =
|
||||
| domContentLoadedEvent
|
||||
@@ -136,6 +147,7 @@ export type event =
|
||||
| fullSnapshotEvent
|
||||
| incrementalSnapshotEvent
|
||||
| metaEvent
|
||||
| logEvent
|
||||
| customEvent;
|
||||
|
||||
export type eventWithTime = event & {
|
||||
@@ -186,6 +198,7 @@ export type recordOptions<T> = {
|
||||
collectFonts?: boolean;
|
||||
// departed, please use sampling options
|
||||
mousemoveWait?: number;
|
||||
recordLog?: boolean | LogRecordOptions;
|
||||
};
|
||||
|
||||
export type observerParam = {
|
||||
@@ -205,6 +218,8 @@ export type observerParam = {
|
||||
styleSheetRuleCb: styleSheetRuleCallback;
|
||||
canvasMutationCb: canvasMutationCallback;
|
||||
fontCb: fontCallback;
|
||||
logCb: logCallback;
|
||||
logOptions: LogRecordOptions;
|
||||
sampling: SamplingStrategy;
|
||||
recordCanvas: boolean;
|
||||
collectFonts: boolean;
|
||||
@@ -222,6 +237,7 @@ export type hooksParam = {
|
||||
styleSheetRule?: styleSheetRuleCallback;
|
||||
canvasMutation?: canvasMutationCallback;
|
||||
font?: fontCallback;
|
||||
log?: logCallback;
|
||||
};
|
||||
|
||||
// https://dom.spec.whatwg.org/#interface-mutationrecord
|
||||
@@ -353,8 +369,67 @@ export type fontParam = {
|
||||
descriptors?: FontFaceDescriptors;
|
||||
};
|
||||
|
||||
export type LogLevel =
|
||||
| 'assert'
|
||||
| 'clear'
|
||||
| 'count'
|
||||
| 'countReset'
|
||||
| 'debug'
|
||||
| 'dir'
|
||||
| 'dirxml'
|
||||
| 'error'
|
||||
| 'group'
|
||||
| 'groupCollapsed'
|
||||
| 'groupEnd'
|
||||
| 'info'
|
||||
| 'log'
|
||||
| 'table'
|
||||
| 'time'
|
||||
| 'timeEnd'
|
||||
| 'timeLog'
|
||||
| 'trace'
|
||||
| 'warn';
|
||||
|
||||
/* fork from interface Console */
|
||||
// all kinds of console functions
|
||||
export type Logger = {
|
||||
assert?: (value: any, message?: string, ...optionalParams: any[]) => void;
|
||||
clear?: () => void;
|
||||
count?: (label?: string) => void;
|
||||
countReset?: (label?: string) => void;
|
||||
debug?: (message?: any, ...optionalParams: any[]) => void;
|
||||
dir?: (obj: any, options?: NodeJS.InspectOptions) => void;
|
||||
dirxml?: (...data: any[]) => void;
|
||||
error?: (message?: any, ...optionalParams: any[]) => void;
|
||||
group?: (...label: any[]) => void;
|
||||
groupCollapsed?: (label?: any[]) => void;
|
||||
groupEnd?: () => void;
|
||||
info?: (message?: any, ...optionalParams: any[]) => void;
|
||||
log?: (message?: any, ...optionalParams: any[]) => void;
|
||||
table?: (tabularData: any, properties?: ReadonlyArray<string>) => void;
|
||||
time?: (label?: string) => void;
|
||||
timeEnd?: (label?: string) => void;
|
||||
timeLog?: (label?: string, ...data: any[]) => void;
|
||||
trace?: (message?: any, ...optionalParams: any[]) => void;
|
||||
warn?: (message?: any, ...optionalParams: any[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* define an interface to replay log records
|
||||
* (data: logData) => void> function to display the log data
|
||||
*/
|
||||
export type ReplayLogger = Partial<Record<LogLevel, (data: logData) => void>>;
|
||||
|
||||
export type LogParam = {
|
||||
level: LogLevel;
|
||||
trace: Array<string>;
|
||||
payload: Array<string>;
|
||||
};
|
||||
|
||||
export type fontCallback = (p: fontParam) => void;
|
||||
|
||||
export type logCallback = (p: LogParam) => void;
|
||||
|
||||
export type viewportResizeDimention = {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -419,6 +494,12 @@ export type playerConfig = {
|
||||
strokeStyle?: string;
|
||||
};
|
||||
unpackFn?: UnpackFn;
|
||||
logConfig: LogReplayConfig;
|
||||
};
|
||||
|
||||
export type LogReplayConfig = {
|
||||
level?: Array<LogLevel> | undefined;
|
||||
replayLogger: ReplayLogger | undefined;
|
||||
};
|
||||
|
||||
export type playerMetaData = {
|
||||
@@ -477,3 +558,20 @@ export type ElementState = {
|
||||
// [scrollLeft,scrollTop]
|
||||
scroll?: [number, number];
|
||||
};
|
||||
|
||||
export type StringifyOptions = {
|
||||
// limit of string length
|
||||
stringLengthLimit?: number;
|
||||
/**
|
||||
* limit of number of keys in an object
|
||||
* if an object contains more keys than this limit, we would call its toString function directly
|
||||
*/
|
||||
numOfKeysLimit: number;
|
||||
};
|
||||
|
||||
export type LogRecordOptions = {
|
||||
level?: Array<LogLevel> | undefined;
|
||||
lengthThreshold?: number;
|
||||
stringifyOptions?: StringifyOptions;
|
||||
logger?: Logger;
|
||||
};
|
||||
|
||||
@@ -2002,6 +2002,720 @@ exports[`ignore 1`] = `
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`log`] = `
|
||||
"[
|
||||
{
|
||||
\\"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\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"assert\\",
|
||||
\\"payload\\": [
|
||||
\\"true\\",
|
||||
\\"\\"assert\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"count\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"count\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"countReset\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"count\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"debug\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"debug\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"dir\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"dir\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"dirxml\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"dirxml\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"group\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"groupCollapsed\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"info\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"info\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"log\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"log\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"table\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"table\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"time\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"timeEnd\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"timeLog\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"trace\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"trace\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"warn\\",
|
||||
\\"payload\\": [
|
||||
\\"\\"warn\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"clear\\",
|
||||
\\"payload\\": []
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`log 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\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"assert\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:2:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"true\\",
|
||||
\\"\\\\\\"assert\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"count\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:3:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"count\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"countReset\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:4:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"count\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"debug\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:5:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"debug\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"dir\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:6:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"dir\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"dirxml\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:7:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"dirxml\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"group\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:8:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"groupCollapsed\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:9:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"info\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:10:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"info\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"log\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:11:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"log\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"table\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:12:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"table\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"time\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:13:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"timeEnd\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:14:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"timeLog\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:15:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"trace\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:16:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"trace\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"warn\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:17:37\\"
|
||||
],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"warn\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 11,
|
||||
\\"level\\": \\"clear\\",
|
||||
\\"trace\\": [
|
||||
\\"__puppeteer_evalu\\",
|
||||
\\"ion_script__:18:37\\"
|
||||
],
|
||||
\\"payload\\": []
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`mask 1`] = `
|
||||
"[
|
||||
{
|
||||
|
||||
10
test/html/log.html
Normal file
10
test/html/log.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Log record</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -29,13 +29,13 @@ describe('record integration tests', function (this: ISuite) {
|
||||
window.Date.now = () => new Date(Date.UTC(2018, 10, 15, 8)).valueOf();
|
||||
window.snapshots = [];
|
||||
rrweb.record({
|
||||
emit: event => {
|
||||
console.log(event);
|
||||
emit: event => {
|
||||
window.snapshots.push(event);
|
||||
},
|
||||
maskAllInputs: ${options.maskAllInputs},
|
||||
maskInputOptions: ${JSON.stringify(options.maskAllInputs)},
|
||||
recordCanvas: ${options.recordCanvas}
|
||||
recordCanvas: ${options.recordCanvas},
|
||||
recordLog: ${options.recordLog},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
@@ -334,4 +334,37 @@ describe('record integration tests', function (this: ISuite) {
|
||||
|
||||
expect(text).to.equal('4\n3\n2\n1\n5');
|
||||
});
|
||||
|
||||
it('can record log mutation', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
await page.setContent(
|
||||
getHtml.call(this, 'log.html', {
|
||||
recordLog: true,
|
||||
}),
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
console.assert(0 == 0, 'assert');
|
||||
console.count('count');
|
||||
console.countReset('count');
|
||||
console.debug('debug');
|
||||
console.dir('dir');
|
||||
console.dirxml('dirxml');
|
||||
console.group();
|
||||
console.groupCollapsed();
|
||||
console.info('info');
|
||||
console.log('log');
|
||||
console.table('table');
|
||||
console.time();
|
||||
console.timeEnd();
|
||||
console.timeLog();
|
||||
console.trace('trace');
|
||||
console.warn('warn');
|
||||
console.clear();
|
||||
});
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'log');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@ describe('record', function (this: ISuite) {
|
||||
document.body.appendChild(span);
|
||||
}, 10);
|
||||
});
|
||||
await this.page.waitFor(50);
|
||||
await this.page.waitFor(100);
|
||||
assertSnapshot(this.events, __filename, 'async-checkout');
|
||||
});
|
||||
|
||||
|
||||
2
typings/record/stringify.d.ts
vendored
Normal file
2
typings/record/stringify.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { StringifyOptions } from '../types';
|
||||
export declare function stringify(obj: any, stringifyOptions?: StringifyOptions): string;
|
||||
2
typings/replay/index.d.ts
vendored
2
typings/replay/index.d.ts
vendored
@@ -42,6 +42,8 @@ export declare class Replayer {
|
||||
private applyMutation;
|
||||
private applyScroll;
|
||||
private applyInput;
|
||||
private formatMessage;
|
||||
private getConsoleLogger;
|
||||
private legacy_resolveMissingNode;
|
||||
private moveAndHover;
|
||||
private drawMouseTail;
|
||||
|
||||
62
typings/types.d.ts
vendored
62
typings/types.d.ts
vendored
@@ -39,6 +39,10 @@ export declare type metaEvent = {
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
export declare type logEvent = {
|
||||
type: EventType.IncrementalSnapshot;
|
||||
data: incrementalData;
|
||||
};
|
||||
export declare type customEvent<T = unknown> = {
|
||||
type: EventType.Custom;
|
||||
data: {
|
||||
@@ -58,7 +62,8 @@ export declare enum IncrementalSource {
|
||||
MediaInteraction = 7,
|
||||
StyleSheetRule = 8,
|
||||
CanvasMutation = 9,
|
||||
Font = 10
|
||||
Font = 10,
|
||||
Log = 11
|
||||
}
|
||||
export declare type mutationData = {
|
||||
source: IncrementalSource.Mutation;
|
||||
@@ -92,8 +97,11 @@ export declare type canvasMutationData = {
|
||||
export declare type fontData = {
|
||||
source: IncrementalSource.Font;
|
||||
} & fontParam;
|
||||
export declare type incrementalData = mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData;
|
||||
export declare type event = domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | customEvent;
|
||||
export declare type logData = {
|
||||
source: IncrementalSource.Log;
|
||||
} & LogParam;
|
||||
export declare type incrementalData = mutationData | mousemoveData | mouseInteractionData | scrollData | viewportResizeData | inputData | mediaInteractionData | styleSheetRuleData | canvasMutationData | fontData | logData;
|
||||
export declare type event = domContentLoadedEvent | loadedEvent | fullSnapshotEvent | incrementalSnapshotEvent | metaEvent | logEvent | customEvent;
|
||||
export declare type eventWithTime = event & {
|
||||
timestamp: number;
|
||||
delay?: number;
|
||||
@@ -123,6 +131,7 @@ export declare type recordOptions<T> = {
|
||||
recordCanvas?: boolean;
|
||||
collectFonts?: boolean;
|
||||
mousemoveWait?: number;
|
||||
recordLog?: boolean | LogRecordOptions;
|
||||
};
|
||||
export declare type observerParam = {
|
||||
mutationCb: mutationCallBack;
|
||||
@@ -141,6 +150,8 @@ export declare type observerParam = {
|
||||
styleSheetRuleCb: styleSheetRuleCallback;
|
||||
canvasMutationCb: canvasMutationCallback;
|
||||
fontCb: fontCallback;
|
||||
logCb: logCallback;
|
||||
logOptions: LogRecordOptions;
|
||||
sampling: SamplingStrategy;
|
||||
recordCanvas: boolean;
|
||||
collectFonts: boolean;
|
||||
@@ -157,6 +168,7 @@ export declare type hooksParam = {
|
||||
styleSheetRule?: styleSheetRuleCallback;
|
||||
canvasMutation?: canvasMutationCallback;
|
||||
font?: fontCallback;
|
||||
log?: logCallback;
|
||||
};
|
||||
export declare type mutationRecord = {
|
||||
type: string;
|
||||
@@ -261,7 +273,36 @@ export declare type fontParam = {
|
||||
buffer: boolean;
|
||||
descriptors?: FontFaceDescriptors;
|
||||
};
|
||||
export declare type LogLevel = 'assert' | 'clear' | 'count' | 'countReset' | 'debug' | 'dir' | 'dirxml' | 'error' | 'group' | 'groupCollapsed' | 'groupEnd' | 'info' | 'log' | 'table' | 'time' | 'timeEnd' | 'timeLog' | 'trace' | 'warn';
|
||||
export declare type Logger = {
|
||||
assert?: (value: any, message?: string, ...optionalParams: any[]) => void;
|
||||
clear?: () => void;
|
||||
count?: (label?: string) => void;
|
||||
countReset?: (label?: string) => void;
|
||||
debug?: (message?: any, ...optionalParams: any[]) => void;
|
||||
dir?: (obj: any, options?: NodeJS.InspectOptions) => void;
|
||||
dirxml?: (...data: any[]) => void;
|
||||
error?: (message?: any, ...optionalParams: any[]) => void;
|
||||
group?: (...label: any[]) => void;
|
||||
groupCollapsed?: (label?: any[]) => void;
|
||||
groupEnd?: () => void;
|
||||
info?: (message?: any, ...optionalParams: any[]) => void;
|
||||
log?: (message?: any, ...optionalParams: any[]) => void;
|
||||
table?: (tabularData: any, properties?: ReadonlyArray<string>) => void;
|
||||
time?: (label?: string) => void;
|
||||
timeEnd?: (label?: string) => void;
|
||||
timeLog?: (label?: string, ...data: any[]) => void;
|
||||
trace?: (message?: any, ...optionalParams: any[]) => void;
|
||||
warn?: (message?: any, ...optionalParams: any[]) => void;
|
||||
};
|
||||
export declare type ReplayLogger = Partial<Record<LogLevel, (data: logData) => void>>;
|
||||
export declare type LogParam = {
|
||||
level: LogLevel;
|
||||
trace: Array<string>;
|
||||
payload: Array<string>;
|
||||
};
|
||||
export declare type fontCallback = (p: fontParam) => void;
|
||||
export declare type logCallback = (p: LogParam) => void;
|
||||
export declare type viewportResizeDimention = {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -316,6 +357,11 @@ export declare type playerConfig = {
|
||||
strokeStyle?: string;
|
||||
};
|
||||
unpackFn?: UnpackFn;
|
||||
logConfig: LogReplayConfig;
|
||||
};
|
||||
export declare type LogReplayConfig = {
|
||||
level?: Array<LogLevel> | undefined;
|
||||
replayLogger: ReplayLogger | undefined;
|
||||
};
|
||||
export declare type playerMetaData = {
|
||||
startTime: number;
|
||||
@@ -361,4 +407,14 @@ export declare type MaskInputFn = (text: string) => string;
|
||||
export declare type ElementState = {
|
||||
scroll?: [number, number];
|
||||
};
|
||||
export declare type StringifyOptions = {
|
||||
stringLengthLimit?: number;
|
||||
numOfKeysLimit: number;
|
||||
};
|
||||
export declare type LogRecordOptions = {
|
||||
level?: Array<LogLevel> | undefined;
|
||||
lengthThreshold?: number;
|
||||
stringifyOptions?: StringifyOptions;
|
||||
logger?: Logger;
|
||||
};
|
||||
export {};
|
||||
|
||||
Reference in New Issue
Block a user