├── .gitignore
├── README.md
├── __tests__
├── fixtures
│ ├── captured-errors.js
│ └── tracekit-resource-spec.js
├── tracekit-computestacktrace-spec.js
├── tracekit-handler-spec.js
├── tracekit-parser-spec.js
└── tracekit-spec.js
├── dist
├── errorWatch.esm.js
├── errorWatch.js
├── errorWatch.min.js
└── errorWatch.min.js.map
├── package-lock.json
├── package.json
├── ref
├── supplement.js
└── tracekit.js
├── rollup.config.js
└── src
├── .babelrc
├── computeStackTrace.js
├── config.js
├── index.js
├── report.js
├── resourceError.js
├── tryCatch.js
├── utils.js
└── wrap.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | *.DS_Store
4 | *.idea
5 | *.vscode
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 前端错误解析
2 |
3 | 根据 [TraceKit](https://github.com/csnover/TraceKit) 改造。
4 | 改动点:
5 |
6 | 1. 使用 `es6` 对源文件进行改写,根据功能拆分成小文件便于维护;
7 | 1. 使用 `rollup` ,方便打成 `UMD`、`ES` 包,压缩代码;
8 | 1. 增加资源加载错误上报;
9 | 1. 测试套件由 `jasmine` 改成了 `Jest`。
10 |
11 | ## 安装
12 |
13 | ```bash
14 | npm i error-watch
15 | ```
16 |
17 | ## 使用
18 |
19 | - es6
20 |
21 | ```javaScript
22 | import ErrorWatch from 'error-watch';
23 |
24 | /**
25 | * 错误监控回调函数
26 | * @param stack {Object|null} 依次根据 Error 对象属性 stacktrace、stack、message和调用链 callers 来解析出错误行列等信息
27 | * @param isWindowError {Boolean} 是否触发 window 监听事件,手动 try/catch 获取 err 解析为 false
28 | * @param error {Error|null} 原始 err
29 | */
30 | function receiveError(stack, isWindowError, error) {
31 | const data = encodeURIComponent(JSON.stringify({
32 | ...stack, // 错误解析的对象
33 | // isWindowError
34 | url: window.location.href, // 报错页面
35 | }));
36 | // img 上报
37 | // 注意分析数据有可能过大,需要考虑 ajax?
38 | new Image().src = 'https://your-websize.com/api/handleError?data=' + data;
39 | }
40 |
41 | // 监听错误
42 | ErrorWatch.report.subscribe(receiveError);
43 | ```
44 |
45 | - 脚本直接引入
46 |
47 | ```html
48 |
49 |
50 | ErrorWatch.report.subscribe(function() {
51 | // code here
52 | });
53 |
54 | ```
55 |
56 | ### 错误回调处理函数,传入三个参数
57 |
58 | - stack,成功是个 `Object` 否则是 `null`,可以用来结合 `SourceMap` 定位错误。
59 | ```json
60 | {
61 | "mode": "stack",
62 | "name": "ReferenceError",
63 | "message": "thisIsAbug is not defined",
64 | "stack": [
65 | {
66 | "url": "http://localhost:7001/public/js/traceKit.min.js",
67 | "func": "Object.makeError",
68 | "args": [],
69 | "line": 1,
70 | "column": 9435,
71 | "context": null
72 | },
73 | {
74 | "url": "http://localhost:7001/public/demo.html",
75 | "func": "?",
76 | "args": [],
77 | "line": 49,
78 | "column": 12,
79 | "context": null
80 | }
81 | ]
82 | }
83 | ```
84 |
85 | - isWindowError,可选上报,区分自动还是手动。由于 try/catch 吐出来的 error 信息丰富,对于定位错误帮助较大,可以为业务逻辑自定义错误。
86 | ```javascript
87 | try {
88 | /*
89 | * your code
90 | */
91 | throw new Error('oops');
92 | } catch (e) {
93 | ErrorWatch.report(e);
94 | }
95 | ```
96 |
97 | - error,原始错误对象,上述的 stack 如果内部解析成功,则例如 stack.stack 已翻译成数组,会抛弃原始的 stack。
98 | 如果需要可以这么做。
99 |
100 | ```javascript
101 | {
102 | ...stack,
103 | errorStack: error && error.stack,
104 | }
105 | ```
106 |
107 | - 完整实例
108 |
109 | ```json
110 | {
111 | "mode": "stack",
112 | "name": "ReferenceError",
113 | "message": "thisIsAbug is not defined",
114 | "stack": [
115 | {
116 | "url": "http://localhost:7001/public/js/traceKit.min.js",
117 | "func": "Object.makeError",
118 | "args": [],
119 | "line": 1,
120 | "column": 9435,
121 | "context": null
122 | },
123 | {
124 | "url": "http://localhost:7001/public/demo.html",
125 | "func": "?",
126 | "args": [],
127 | "line": 49,
128 | "column": 12,
129 | "context": null
130 | }
131 | ],
132 | "errorStack": "ReferenceError: thisIsAbug is not defined\n at Object.makeError (http://localhost:7001/public/js/traceKit.min.js:1:9435)\n at http://localhost:7001/public/demo.html:49:12",
133 | "url": "http://localhost:7001/public/demo.html"
134 | }
135 | ```
136 |
137 | ### 资源加载错误上报信息
138 | - stack,根据 `mode` 是 `resource`,来区分资源加载错误。
139 | ````json
140 | {
141 | "message": "img is load error",
142 | "mode": "resource",
143 | "name": "http://domain/404.jpg",
144 | "stack": null,
145 | "url": "http://localhost:7001/public/demo.html"
146 | }
147 | ````
148 | ### 建议
149 | - 尽量不用匿名函数,都给它加个名字,便于错误定位。
150 | ```javascript
151 | Api.foo = function Api_foo() {
152 | };
153 | const bar = function barFn() {
154 | };
155 | ```
156 | - Script error. 跨域脚本无法拿到错误信息。
157 | 1. 跨源资源共享机制 `CORS` :`Access-Control-Allow-Origin: Your-allow-origin`
158 | 1. 脚本属性 `crossOrigin` :``
159 |
160 | ## npm scripts
161 |
162 | - `npm run build` 根据 `rollup.config.js` 配置文件进行打包。
163 | - `npm test` 单元测试。
164 |
165 | ## 阅读源码
166 |
167 | 阅读源码前,可以参考下关于JS错误知识的一些讨论 [错误监控原理分析](https://github.com/Godiswill/blog/issues/7)。
168 |
--------------------------------------------------------------------------------
/__tests__/fixtures/captured-errors.js:
--------------------------------------------------------------------------------
1 | /* exported CapturedExceptions */
2 | const CapturedExceptions = {};
3 |
4 | CapturedExceptions.OPERA_854 = {
5 | message: "Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n" +
6 | "Backtrace:\n" +
7 | " Line 44 of linked script http://path/to/file.js\n" +
8 | " this.undef();\n" +
9 | " Line 31 of linked script http://path/to/file.js\n" +
10 | " ex = ex || this.createException();\n" +
11 | " Line 18 of linked script http://path/to/file.js\n" +
12 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
13 | " Line 4 of inline#1 script in http://path/to/file.js\n" +
14 | " printTrace(printStackTrace());\n" +
15 | " Line 7 of inline#1 script in http://path/to/file.js\n" +
16 | " bar(n - 1);\n" +
17 | " Line 11 of inline#1 script in http://path/to/file.js\n" +
18 | " bar(2);\n" +
19 | " Line 15 of inline#1 script in http://path/to/file.js\n" +
20 | " foo();\n" +
21 | "",
22 | 'opera#sourceloc': 44
23 | };
24 |
25 | CapturedExceptions.OPERA_902 = {
26 | message: "Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n" +
27 | "Backtrace:\n" +
28 | " Line 44 of linked script http://path/to/file.js\n" +
29 | " this.undef();\n" +
30 | " Line 31 of linked script http://path/to/file.js\n" +
31 | " ex = ex || this.createException();\n" +
32 | " Line 18 of linked script http://path/to/file.js\n" +
33 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
34 | " Line 4 of inline#1 script in http://path/to/file.js\n" +
35 | " printTrace(printStackTrace());\n" +
36 | " Line 7 of inline#1 script in http://path/to/file.js\n" +
37 | " bar(n - 1);\n" +
38 | " Line 11 of inline#1 script in http://path/to/file.js\n" +
39 | " bar(2);\n" +
40 | " Line 15 of inline#1 script in http://path/to/file.js\n" +
41 | " foo();\n" +
42 | "",
43 | 'opera#sourceloc': 44
44 | };
45 |
46 | CapturedExceptions.OPERA_927 = {
47 | message: "Statement on line 43: Type mismatch (usually a non-object value used where an object is required)\n" +
48 | "Backtrace:\n" +
49 | " Line 43 of linked script http://path/to/file.js\n" +
50 | " bar(n - 1);\n" +
51 | " Line 31 of linked script http://path/to/file.js\n" +
52 | " bar(2);\n" +
53 | " Line 18 of linked script http://path/to/file.js\n" +
54 | " foo();\n" +
55 | "",
56 | 'opera#sourceloc': 43
57 | };
58 |
59 | CapturedExceptions.OPERA_964 = {
60 | message: "Statement on line 42: Type mismatch (usually non-object value supplied where object required)\n" +
61 | "Backtrace:\n" +
62 | " Line 42 of linked script http://path/to/file.js\n" +
63 | " this.undef();\n" +
64 | " Line 27 of linked script http://path/to/file.js\n" +
65 | " ex = ex || this.createException();\n" +
66 | " Line 18 of linked script http://path/to/file.js: In function printStackTrace\n" +
67 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
68 | " Line 4 of inline#1 script in http://path/to/file.js: In function bar\n" +
69 | " printTrace(printStackTrace());\n" +
70 | " Line 7 of inline#1 script in http://path/to/file.js: In function bar\n" +
71 | " bar(n - 1);\n" +
72 | " Line 11 of inline#1 script in http://path/to/file.js: In function foo\n" +
73 | " bar(2);\n" +
74 | " Line 15 of inline#1 script in http://path/to/file.js\n" +
75 | " foo();\n" +
76 | "",
77 | 'opera#sourceloc': 42,
78 | stacktrace: " ... Line 27 of linked script http://path/to/file.js\n" +
79 | " ex = ex || this.createException();\n" +
80 | " Line 18 of linked script http://path/to/file.js: In function printStackTrace\n" +
81 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
82 | " Line 4 of inline#1 script in http://path/to/file.js: In function bar\n" +
83 | " printTrace(printStackTrace());\n" +
84 | " Line 7 of inline#1 script in http://path/to/file.js: In function bar\n" +
85 | " bar(n - 1);\n" +
86 | " Line 11 of inline#1 script in http://path/to/file.js: In function foo\n" +
87 | " bar(2);\n" +
88 | " Line 15 of inline#1 script in http://path/to/file.js\n" +
89 | " foo();\n" +
90 | ""
91 | };
92 |
93 | CapturedExceptions.OPERA_10 = {
94 | message: "Statement on line 42: Type mismatch (usually non-object value supplied where object required)",
95 | 'opera#sourceloc': 42,
96 | stacktrace: " Line 42 of linked script http://path/to/file.js\n" +
97 | " this.undef();\n" +
98 | " Line 27 of linked script http://path/to/file.js\n" +
99 | " ex = ex || this.createException();\n" +
100 | " Line 18 of linked script http://path/to/file.js: In function printStackTrace\n" +
101 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
102 | " Line 4 of inline#1 script in http://path/to/file.js: In function bar\n" +
103 | " printTrace(printStackTrace());\n" +
104 | " Line 7 of inline#1 script in http://path/to/file.js: In function bar\n" +
105 | " bar(n - 1);\n" +
106 | " Line 11 of inline#1 script in http://path/to/file.js: In function foo\n" +
107 | " bar(2);\n" +
108 | " Line 15 of inline#1 script in http://path/to/file.js\n" +
109 | " foo();\n" +
110 | ""
111 | };
112 |
113 | CapturedExceptions.OPERA_11 = {
114 | message: "'this.undef' is not a function",
115 | stack: "([arguments not available])@http://path/to/file.js:27\n" +
116 | "bar([arguments not available])@http://domain.com:1234/path/to/file.js:18\n" +
117 | "foo([arguments not available])@http://domain.com:1234/path/to/file.js:11\n" +
118 | "@http://path/to/file.js:15\n" +
119 | "Error created at @http://path/to/file.js:15",
120 | stacktrace: "Error thrown at line 42, column 12 in () in http://path/to/file.js:\n" +
121 | " this.undef();\n" +
122 | "called from line 27, column 8 in (ex) in http://path/to/file.js:\n" +
123 | " ex = ex || this.createException();\n" +
124 | "called from line 18, column 4 in printStackTrace(options) in http://path/to/file.js:\n" +
125 | " var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
126 | "called from line 4, column 5 in bar(n) in http://path/to/file.js:\n" +
127 | " printTrace(printStackTrace());\n" +
128 | "called from line 7, column 4 in bar(n) in http://path/to/file.js:\n" +
129 | " bar(n - 1);\n" +
130 | "called from line 11, column 4 in foo() in http://path/to/file.js:\n" +
131 | " bar(2);\n" +
132 | "called from line 15, column 3 in http://path/to/file.js:\n" +
133 | " foo();"
134 | };
135 |
136 | CapturedExceptions.OPERA_12 = {
137 | message: "Cannot convert 'x' to object",
138 | stack: "([arguments not available])@http://localhost:8000/ExceptionLab.html:48\n" +
139 | "dumpException3([arguments not available])@http://localhost:8000/ExceptionLab.html:46\n" +
140 | "([arguments not available])@http://localhost:8000/ExceptionLab.html:1",
141 | stacktrace: "Error thrown at line 48, column 12 in (x) in http://localhost:8000/ExceptionLab.html:\n" +
142 | " x.undef();\n" +
143 | "called from line 46, column 8 in dumpException3() in http://localhost:8000/ExceptionLab.html:\n" +
144 | " dumpException((function(x) {\n" +
145 | "called from line 1, column 0 in (event) in http://localhost:8000/ExceptionLab.html:\n" +
146 | " dumpException3();"
147 | };
148 |
149 | CapturedExceptions.OPERA_25 = {
150 | message: "Cannot read property 'undef' of null",
151 | name: "TypeError",
152 | stack: "TypeError: Cannot read property 'undef' of null\n" +
153 | " at http://path/to/file.js:47:22\n" +
154 | " at foo (http://path/to/file.js:52:15)\n" +
155 | " at bar (http://path/to/file.js:108:168)"
156 | };
157 |
158 | CapturedExceptions.CHROME_15 = {
159 | 'arguments': ["undef"],
160 | message: "Object #