Skip to content

Commit

Permalink
fix: fix console plugin's OOM problem (rrweb-io#656)
Browse files Browse the repository at this point in the history
* fix: fix console plugin's OOM problem

* fix: fix console plugin

* feat: patch

* feat: patch

* feat: patch

Co-authored-by: chenyangbj01 <chenyangbj01@fenbi.com>
  • Loading branch information
PeterChen1997 and chenyangbj01 authored Aug 7, 2021
1 parent 838287a commit 8d40e52
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ temp
.env

.DS_Store

build
dist
13 changes: 7 additions & 6 deletions docs/recipes/console.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,20 @@ rrweb.record({
stringifyOptions: {
stringLengthLimit: 1000,
numOfKeysLimit: 100,
depthOfLimit: 1
},
logger: window.console,
})],
});
```

All recordLog options are described below:
| key | default | description |
| ---------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | Default value contains names of all console functions. You can override it by setting console levels you need. |
| lengthThreshold | 1000 | Maximum number of records of console output. |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50 } | If console output includes js objects, we need to stringify them. `stringLengthLimit` limits the string length of single value. `numOfKeysLimit` limits the number of keys in an object. If an object contains more keys than this limit, we would only save object's name. You can reduce the size of events by setting these options. |
| logger | window.console | the console object we would record.You can set a console object from another execution environment where you would like to record. |
| key | default | description |
| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | Default value contains names of all console functions. You can override it by setting console levels you need. |
| lengthThreshold | 1000 | Maximum number of records of console output. |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50, depthOfLimit: 4 } | If console output includes js objects, we need to stringify them. `stringLengthLimit` limits the string length of single value. `numOfKeysLimit` limits the number of keys in an object. `depthOfLimit` limits the depth of object. If an object contains more keys than this limit, we would only save object's name. You can reduce the size of events by setting these options. |
| logger | window.console | the console object we would record.You can set a console object from another execution environment where you would like to record. |

## replay console

Expand Down
13 changes: 7 additions & 6 deletions docs/recipes/console.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,20 @@ rrweb.record({
stringifyOptions: {
stringLengthLimit: 1000,
numOfKeysLimit: 100,
depthOfLimit: 1
},
logger: window.console,
})],
});
```

如下是配置选项的详细说明:
| key | 默认值 | 功能 |
| ---------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | 默认值包含了 console 的全部函数,你也可以传入想要录制的 console 函数。 |
| lengthThreshold | 1000 | 录制 console 输出信息的最大条数。 |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50 } | 如果 console 输出包含了 js 对象,我们需要对其进行序列化,`stringLengthLimit` 限制了单个值能转化的最大字符串长度,`numOfKeysLimit` 限制了一个被序列化的 js 对象能够包含的最大数量 key,如果对象的 key 数量超过了这个限制,我们将只保留对象的名字。你能通过这些选项来减小生成的 events 的体积。 |
| logger | window.console | 要录制的 console 对象,你也可以传入一个想要录制的其他 js 执行环境的 console 对象。 |
| key | 默认值 | 功能 |
| ---------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| level | ['log','warn','error',...] | 默认值包含了 console 的全部函数,你也可以传入想要录制的 console 函数。 |
| lengthThreshold | 1000 | 录制 console 输出信息的最大条数。 |
| stringifyOptions | { stringLengthLimit: undefined, numOfKeysLimit: 50, depthOfLimit: 4 } | 如果 console 输出包含了 js 对象,我们需要对其进行序列化,`stringLengthLimit` 限制了单个值能转化的最大字符串长度,`numOfKeysLimit` 限制了一个被序列化的 js 对象能够包含的最大数量 key`depthOfLimit` 限制了一个被序列化的 js 对象能够拥有的对象深度(层数),如果对象的 key 数量超过了这个限制,我们将只保留对象的名字。你能通过这些选项来减小生成的 events 的体积。 |
| logger | window.console | 要录制的 console 对象,你也可以传入一个想要录制的其他 js 执行环境的 console 对象。 |

## 播放 console 数据

Expand Down
9 changes: 7 additions & 2 deletions packages/rrweb/src/plugins/console/record/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { listenerHandler, RecordPlugin } from '../../../types';
import { stringify } from './stringify';
import { StackFrame, ErrorStackParser } from './error-stack-parser';
import { patch } from '../../../utils';
import { ErrorStackParser, StackFrame } from './error-stack-parser';
import { stringify } from './stringify';

export type StringifyOptions = {
// limit of string length
Expand All @@ -11,6 +11,11 @@ export type StringifyOptions = {
* if an object contains more keys than this limit, we would call its toString function directly
*/
numOfKeysLimit: number;
/**
* limit number of depth in an object
* if an object is too deep, toString process may cause browser OOM
*/
depthOfLimit: number;
};

type LogRecordOptions = {
Expand Down
50 changes: 43 additions & 7 deletions packages/rrweb/src/plugins/console/record/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ function pathToSelector(node: HTMLElement): string | '' {
return path;
}

/**
* judge is object
*/
function isObject(obj: any): boolean {
return Object.prototype.toString.call(obj) === '[object Object]';
}

/**
* judge the object's depth
*/
function isObjTooDeep(obj: any, limit: number): boolean {
if (limit === 0) {
return true;
}

const keys = Object.keys(obj);
for (const key of keys) {
if (isObject(obj[key]) && isObjTooDeep(obj[key], limit - 1)) {
return true;
}
}

return false;
}

/**
* stringify any js object
* @param obj the object to stringify
Expand All @@ -58,6 +83,7 @@ export function stringify(
): string {
const options: StringifyOptions = {
numOfKeysLimit: 50,
depthOfLimit: 4,
};
Object.assign(options, stringifyOptions);
const stack: any[] = [];
Expand Down Expand Up @@ -89,7 +115,7 @@ export function stringify(
if (value === null || value === undefined) {
return value;
}
if (shouldToString(value)) {
if (shouldIgnore(value)) {
return toString(value);
}
if (value instanceof Event) {
Expand All @@ -115,18 +141,28 @@ export function stringify(
});

/**
* whether we should call toString function of this object
* whether we should ignore obj's info and call toString() function instead
*/
function shouldToString(_obj: object): boolean {
if (
typeof _obj === 'object' &&
Object.keys(_obj).length > options.numOfKeysLimit
) {
function shouldIgnore(_obj: object): boolean {
// outof keys limit
if (isObject(_obj) && Object.keys(_obj).length > options.numOfKeysLimit) {
return true;
}

// is function
if (typeof _obj === 'function') {
return true;
}

/**
* judge object's depth to avoid browser's OOM
*
* issues: https://github.com/rrweb-io/rrweb/issues/653
*/
if (isObject(_obj) && isObjTooDeep(_obj, options.depthOfLimit)) {
return true;
}

return false;
}

Expand Down
4 changes: 3 additions & 1 deletion packages/rrweb/typings/plugins/console/record/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RecordPlugin } from '../../../types';
export declare type StringifyOptions = {
stringLengthLimit?: number;
numOfKeysLimit: number;
depthOfLimit: number;
};
declare type LogRecordOptions = {
level?: LogLevel[] | undefined;
Expand Down Expand Up @@ -38,4 +39,5 @@ export declare type Logger = {
};
export declare const PLUGIN_NAME = "rrweb/console@1";
export declare const getRecordConsolePlugin: (options?: LogRecordOptions) => RecordPlugin;
export {};
export { };

0 comments on commit 8d40e52

Please sign in to comment.