├── LICENSE
├── README.md
├── _config.yml
├── example
└── index.html
├── package.json
└── src
├── error.js
└── error.min.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 ecitlm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-log-report
2 |
3 | > 前端错误日志采集上报、上报给后端分析错误日、主要用于移动端各手机类型错误日志的收集分析
4 |
5 | ### 为何要做错误日志追踪上报 (业务背景)
6 |
7 | > 前端 JS 代码错误,浏览器都都会在控制台输出错误信息,以及出错的文件,行号,堆栈信息,这些错误很容易导致页面代码不执行,并且考虑到手机类型五花八门,浏览器内核以及版本的差异性,前端代码机型兼容性问题,并不能将所有的手机都拿来适配,前端错误日志上报是一个较好的解决方案
8 |
9 | ### 安装 Installation
10 |
11 | **直接下载**
12 | 点击下载 [error.js](https://github.com/ecitlm/js-log-report/blob/master/src/error.min.js)直接在你的页面中引用
13 |
14 | ```
15 |
16 | ```
17 |
18 | 或者引用 `jsDelivr CDN:`
19 |
20 | ```
21 |
22 | ```
23 |
24 | **npm 模块安装**
25 |
26 | ```
27 | npm install js-log-report
28 | ```
29 |
30 | ```
31 | import errLogReport from 'js-log-report'
32 | // 方法调用查看 如何使用
33 |
34 | ```
35 |
36 | ### 日志上报哪些数据
37 |
38 | 1.通过 `window.onerror` 可以获取 `msg, url, line, col, error`等错误信息,JS 的错误行号、url 错误地址, 2.通过 `window.navigator.userAgent` 获取 设备浏览器的信息集合
39 | 如:
40 |
41 | ```
42 | User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1
43 | ```
44 |
45 | 3. `os_version` 系统版本号
46 | 4. `browser` 浏览器类型 `Opera` `FF` `Chrome` `Safari` `IE`
47 |
48 | ```javascript
49 | var defaults = {
50 | ua: window.navigator.userAgent,
51 | browser: '',
52 | os: '',
53 | osVersion: '',
54 | errUrl: window.location.href,
55 | msg: '', // 错误的具体信息
56 | url: '', // 错误所在的url
57 | line: '', // 错误所在的行
58 | col: '', // 错误所在的列
59 | error: '' // 具体的error对象
60 | }
61 | ```
62 |
63 | 具体上报字段可查看表结构
64 |
65 | ### 如何实现错误上报
66 |
67 | 1.实现错误日志收集 收集 onerror 错误参数,以及自定义的参数 2.核心`window.onerror`,重写该方法、在此中捕获异常错误信息、并且将错误信息发送至服务器接口
68 | 大致代码如下
69 |
70 | ```javascript
71 | window.onerror = function(msg, url, line, col, error) {
72 | ajax({
73 | url: 'xxx/api/sendError', // 请求地址
74 | type: 'POST', // 请求方式
75 | data: data, // 请求参数
76 | dataType: 'json',
77 | success: function(response, xml) {
78 | // success
79 | },
80 | fail: function(status) {
81 | // error
82 | }
83 | })
84 | }
85 | ```
86 |
87 | ### 如何使用
88 |
89 | > 使用如`index.html`所示,导入以下代码在页面 head 中,并且优先于其他 JS 文件加载
90 |
91 | ```html
92 |
93 |
103 | ```
104 |
105 | 
106 |
107 | ### 数据上报表结构
108 |
109 | ```mysql
110 | DROP TABLE IF EXISTS `j_log`;
111 | CREATE TABLE `j_log` (
112 | `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id号',
113 | `os_version` char(10) DEFAULT NULL COMMENT '系统版本号',
114 | `msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
115 | `error_url` varchar(255) DEFAULT NULL COMMENT '错误所在的url',
116 | `line` int(10) DEFAULT NULL COMMENT '错误所在的行',
117 | `col` int(10) DEFAULT NULL COMMENT '错误所在的列',
118 | `error` varchar(255) DEFAULT NULL COMMENT '具体的error对象',
119 | `url` varchar(255) DEFAULT NULL,
120 | `browser` varchar(255) DEFAULT NULL COMMENT '浏览器类型',
121 | `product_name` char(255) CHARACTER SET utf8 DEFAULT '' COMMENT '产品名称',
122 | `error_time` char(20) DEFAULT NULL COMMENT '时间戳',
123 | `os` char(10) DEFAULT NULL COMMENT '系统类型',
124 | `extend` varchar(255) DEFAULT NULL COMMENT '业务扩展字段、保存JSON字符串',
125 | `ua` varchar(255) DEFAULT NULL,
126 | PRIMARY KEY (`id`)
127 | ) ENGINE=MyISAM AUTO_INCREMENT=55 DEFAULT CHARSET=utf8;
128 |
129 | ```
130 |
131 | ### 源代码
132 |
133 | [GitHub](https://github.com/ecitlm/js-log-report)
134 |
135 | ### 缺点
136 |
137 | 对于压缩的代码,报错信息没法定位到具体是什么地方报错了,这里没有去详细研究,阮一峰老师有篇相关文章
138 | [JavaScript Source Map 详解](http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html)
139 |
140 | #### License
141 |
142 | MIT licensed.
143 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Examples-errLogReport
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 | 请查看控制台可看到上报数据
27 |
28 |
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-log-report",
3 | "version": "1.0.2",
4 | "description": "js-log-report",
5 | "main": "src/error.min.js",
6 | "directories": {
7 | "example": "example"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/ecitlm/js-log-report.git"
15 | },
16 | "author": "ecitlm",
17 | "keywords": [
18 | "jslog",
19 | "badjs",
20 | "js-log-report",
21 | "errorlog"
22 | ],
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/ecitlm/js-log-report/issues"
26 | },
27 | "homepage": "https://github.com/ecitlm/js-log-report#readme"
28 | }
29 |
--------------------------------------------------------------------------------
/src/error.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: ecitlm
3 | * @Date: 2018-06-21 09:39:59
4 | * @Last Modified by: ecitlm
5 | * @Last Modified time: 2018-06-21 13:36:53
6 | */
7 | ;(function () {
8 | if (window.errLogReport) {
9 | return window.errLogReport
10 | }
11 | /*
12 | *ajax封装
13 | */
14 | function ajax (options) {
15 | options = options || {}
16 | options.type = (options.type || 'GET').toUpperCase()
17 | options.dataType = options.dataType || 'json'
18 | var params = formatParams(options.data)
19 |
20 | if (window.XMLHttpRequest) {
21 | var xhr = new XMLHttpRequest()
22 | } else {
23 | var xhr = new ActiveXObject('Microsoft.XMLHTTP')
24 | }
25 |
26 | xhr.onreadystatechange = function () {
27 | if (xhr.readyState == 4) {
28 | var status = xhr.status
29 | if (status >= 200 && status < 300) {
30 | options.success && options.success(xhr.responseText, xhr.responseXML)
31 | } else {
32 | options.fail && options.fail(status)
33 | }
34 | }
35 | }
36 |
37 | if (options.type == 'GET') {
38 | xhr.open('GET', options.url + '?' + params, true)
39 | xhr.send(null)
40 | } else if (options.type == 'POST') {
41 | xhr.open('POST', options.url, true)
42 | // 设置表单提交时的内容类型
43 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
44 | xhr.send(params)
45 | }
46 | }
47 |
48 | /*
49 | *格式化参数
50 | */
51 | function formatParams (data) {
52 | var arr = []
53 | for (var name in data) {
54 | arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]))
55 | }
56 | arr.push(('v=' + Math.random()).replace('.', ''))
57 | return arr.join('&')
58 | }
59 |
60 | /*
61 | * 合并对象,将配置的参数也一并上报
62 | */
63 | function cloneObj (oldObj) { // 复制对象方法
64 | if (typeof (oldObj) !== 'object') return oldObj
65 | if (oldObj == null) return oldObj
66 | var newObj = new Object()
67 | for (var prop in oldObj) { newObj[prop] = oldObj[prop] }
68 | return newObj
69 | };
70 |
71 | function extendObj () { // 扩展对象
72 | var args = arguments
73 | if (args.length < 2) { return }
74 | var temp = cloneObj(args[0]) // 调用复制对象方法
75 | for (var n = 1, len = args.length; n < len; n++) {
76 | for (var index in args[n]) {
77 | temp[index] = args[n][index]
78 | }
79 | }
80 | return temp
81 | }
82 |
83 | function getSystemVersion () {
84 | var ua = window.navigator.userAgent
85 | if (ua.indexOf('CPU iPhone OS ') >= 0) {
86 | return ua.substring(ua.indexOf('CPU iPhone OS ') + 14, ua.indexOf(' like Mac OS X'))
87 | } else if (ua.indexOf('Android ') >= 0) {
88 | return ua.substr(ua.indexOf('Android ') + 8, 3)
89 | } else {
90 | return 'other'
91 | }
92 | }
93 |
94 | /**
95 | 获取浏览器类型
96 | */
97 | function getBrowser () {
98 | var userAgent = navigator.userAgent // 取得浏览器的userAgent字符串
99 | var isOpera = userAgent.indexOf('Opera') > -1
100 | if (isOpera) {
101 | return 'Opera'
102 | }; // 判断是否Opera浏览器
103 | if (userAgent.indexOf('Firefox') > -1) {
104 | return 'FF'
105 | } // 判断是否Firefox浏览器
106 | if (userAgent.indexOf('Chrome') > -1) {
107 | return 'Chrome'
108 | }
109 | if (userAgent.indexOf('Safari') > -1) {
110 | return 'Safari'
111 | } // 判断是否Safari浏览器
112 | if (userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 && !isOpera) {
113 | return 'IE'
114 | }; // 判断是否IE浏览器
115 | }
116 |
117 | /**
118 | 获取设备是安卓、IOS 还是PC端
119 | */
120 | function getDevices () {
121 | var u = navigator.userAgent, app = navigator.appVersion
122 | if (/AppleWebKit.*Mobile/i.test(navigator.userAgent) || (/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/.test(navigator.userAgent))) {
123 | if (window.location.href.indexOf('?mobile') < 0) {
124 | try {
125 | if (/iPhone|mac|iPod|iPad/i.test(navigator.userAgent)) {
126 | return 'iPhone'
127 | } else {
128 | return 'Android'
129 | }
130 | } catch (e) {}
131 | }
132 | } else if (u.indexOf('iPad') > -1) {
133 | return 'iPhone'
134 | } else {
135 | return 'Android'
136 | }
137 | }
138 |
139 | /*
140 | * 默认上报的错误信息
141 | */
142 | var defaults = {
143 | ua: window.navigator.userAgent,
144 | browser: getBrowser(),
145 | os: getDevices(),
146 | osVersion: getSystemVersion(),
147 | errUrl: window.location.href,
148 | msg: '', // 错误的具体信息
149 | url: '', // 错误所在的url
150 | line: '', // 错误所在的行
151 | col: '', // 错误所在的列
152 | error: '' // 具体的error对象
153 | }
154 |
155 | /**
156 | * 核心代码区
157 | **/
158 | var errLogReport = function (params) {
159 | if (!params.url) { return }
160 | window.onerror = function (msg, url, line, col, error) {
161 | // 采用异步的方式,避免阻塞
162 | setTimeout(function () {
163 | // 不一定所有浏览器都支持col参数,如果不支持就用window.event来兼容
164 | col = col || (window.event && window.event.errorCharacter) || 0
165 |
166 | defaults.url = url
167 | defaults.line = line
168 | defaults.col = col
169 |
170 | if (error && error.stack) {
171 | // 如果浏览器有堆栈信息,直接使用
172 | defaults.msg = error.stack.toString()
173 | } else if (arguments.callee) {
174 | // 尝试通过callee拿堆栈信息
175 | var ext = []
176 | var fn = arguments.callee.caller
177 | var floor = 3 // 这里只拿三层堆栈信息
178 | while (fn && (--floor > 0)) {
179 | ext.push(fn.toString())
180 | if (fn === fn.caller) {
181 | break// 如果有环
182 | }
183 | fn = fn.caller
184 | }
185 | defaults.msg = ext.join(',')
186 | }
187 | // 合并上报的数据,包括默认上报的数据和自定义上报的数据
188 | var reportData = extendObj(params.data || {}, defaults)
189 | console.log(reportData)
190 |
191 | // 把错误信息发送给后台
192 | ajax({
193 | url: params.url, // 请求地址
194 | type: 'POST', // 请求方式
195 | data: reportData, // 请求参数
196 | dataType: 'json',
197 | success: function (response, xml) {
198 | // 此处放成功后执行的代码
199 | params.successCallBack && params.successCallBack(response, xml)
200 | },
201 | fail: function (status) {
202 | // 此处放失败后执行的代码
203 | params.failCallBack && params.failCallBack(status)
204 | }
205 | })
206 | }, 0)
207 |
208 | return true // 错误不会console浏览器上,如需要,可将这样注释
209 | }
210 | }
211 |
212 | window.errLogReport = errLogReport
213 | })()
214 |
215 | if (typeof(module) !== 'undefined'){
216 | module.exports = window.errLogReport
217 | }
218 | else if (typeof define === 'function' && define.amd) {
219 | define([], function () {
220 | 'use strict'
221 | return window.errLogReport
222 | })
223 | }
--------------------------------------------------------------------------------
/src/error.min.js:
--------------------------------------------------------------------------------
1 | "use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};!function(){if(window.errLogReport)return window.errLogReport;function u(n){(n=n||{}).type=(n.type||"GET").toUpperCase(),n.dataType=n.dataType||"json";var e=function(e){var n=[];for(var o in e)n.push(encodeURIComponent(o)+"="+encodeURIComponent(e[o]));return n.push(("v="+Math.random()).replace(".","")),n.join("&")}(n.data);if(window.XMLHttpRequest)var o=new XMLHttpRequest;else o=new ActiveXObject("Microsoft.XMLHTTP");o.onreadystatechange=function(){if(4==o.readyState){var e=o.status;200<=e&&e<300?n.success&&n.success(o.responseText,o.responseXML):n.fail&&n.fail(e)}},"GET"==n.type?(o.open("GET",n.url+"?"+e,!0),o.send(null)):"POST"==n.type&&(o.open("POST",n.url,!0),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.send(e))}function c(){var e=arguments;if(!(e.length<2)){for(var n=function(e){if("object"!==(void 0===e?"undefined":_typeof(e)))return e;if(null==e)return e;var n=new Object;for(var o in e)n[o]=e[o];return n}(e[0]),o=1,r=e.length;o