最近在写谬误上报,记录一下,如果对你有所帮忙,荣幸之至;第一次写,有点啰嗦,见谅!
大略分为三个局部:
- 谬误收集
- 谬误筛选
- 谬误上报
- 注意事项
- 残缺示例
一、谬误收集
js的谬误个别分为:运行时谬误、资源加载谬误、网络申请谬误;
对于语法错误、资源加载谬误,供咱们抉择的谬误收集形式个别是:
window.addEventListener('error', e => {}, true);window.onerror = function (msg, url, line, col, error) {}
**划重点:**
- 两者取得的参数不一样;
- window.addEventListener能监测到资源(css,img,script)加载失败;
- window.addEventListener能捕捉到window.onerror能捕捉到的谬误;
- 二者都不能捕捉到console.error的错误信息;
- 二者都不能捕捉到:当promise被reject并且错误信息没有被解决时的错误信息;
因而咱们能够这样收集谬误:
window.addEventListener("error", e => { if (!e) { return; } console.log(e.message);//错误信息 conosle.log(e.filename);//产生谬误的文件名 console.log(e.lineno);//产生谬误的行号(代码压缩合并对值有影响) console.log(e.colno);//产生谬误的列号(代码压缩合并对值有影响) const _target = e.target || e.srcElement; if (!_target) { return; } if (_target === window) { //语法错误 let _error = e.error; if (_error) { console.log(_error.stack);//谬误的堆栈信息 } } else { // 元素谬误,比方援用资源报错 let _src = _target.src; console.log(_src);//_src: 谬误的资源门路 } }, true);
当是运行时的语法错误时,咱们能够拿到报错的行号,列号,错误信息,谬误堆栈,以及产生谬误的脚本的门路及名字。
当是资源门路谬误时,咱们能够拿到谬误资源的门路及名字。
至此,咱们就拿到了想要的资源加载谬误、运行时语法错误的信息,那ajax网络申请谬误怎么办呢?
此时:有两个形式能够抉择
throw new Error('抛出的一个谬误');console.error('打印一个谬误');//上面会讲
咱们后面定义的办法能够收集到throw new Error
抛出的谬误,然而要留神,抛出谬误同样也会阻断后续的程序,应用的时候要小心;如果你的我的项目中也封装了http申请的话,可参照上面代码:
//基于jquery function ajaxFun (params) { var _d = { type: params.type || 'POST', url: params.url || '', data: params.data || null, dataType: params.dataType || 'JSON', contentType: params.contentType || 'application/x-www-form-urlencoded', beforeSend: function (request) { }, success: function (data, status, xhr) { }, error: function (xhr, type, error) { throw new Error(params.url + '申请失败'); } } $.ajax(_d); }
下面的代码是用jquery
封装的申请,我在error
办法外面抛出了这个ajax申请的谬误,因为抛出谬误前面没有其余业务逻辑,不会有什么问题,这里我只要求收集ajax的error
办法谬误,如果你的我的项目要求解决所有异样谬误,比方token生效导致的登陆失败,就须要在success函数外面也做解决了。然而,要留神throw new Error('抛出的一个谬误')
与console.error('打印一个谬误')
的区别。
当应用console.error打印谬误时,后面的window.addEventListener
形式没法收集到,然而咱们能够通过其余形式收集到谬误,上面是一个更非凡的例子;
**特例:**
js使用范畴很广,有些状况,这样是不可能收集到咱们想要的谬误的;
打个比方,咱们用 cocos creator
引擎写游戏时,加载资源是应用引擎的办法,当产生资源不存在的谬误时,咱们是不晓得的,然而,咱们发现 cocos creator
引擎会将谬误打印到控制台,那也是引擎做的操作,咱们一番顺藤摸瓜,会发现,cocos creator
引擎在底层报错都是用cc.error
,翻看cc.error
的源码,咱们就看见了咱们想看见的货色了console.error()
,这样一来,晓得谬误是怎么来的,就好办了。(具体情况,具体看待,这里只是凑巧cocos是这么解决的,其余引擎可能不太一样)
let _windowError = window.console.error;window.console.error = function () { let _str = JSON.stringify(arguments); console.log(_str); _windowError && _windowError.apply(window, arguments);}
复写console.error
后,无论和人在何处应用这个函数,咱们都能够保障这个打印被咱们解决过,
记住,肯定要先将原来的console.error
接管一下,并且在实现咱们须要的业务后,执行原来console.error
,
保障不会影响到其余的逻辑。
二、谬误筛选
兴许你会纳闷?不是所有的谬误都上报么,为什么要筛选呢?
大多数状况,咱们收集到谬误,而后上报即可,
然而,有时候,会有循环报错
、资源加载失败始终重试,始终失败
等种种非凡状况,如果依照失常的上报流程,那么可能会产生在短短几秒的工夫内,收集到了上千、上万条数据,导致程序卡顿,甚至是解体。
因而,咱们须要对谬误进行筛选。
let _errorMap = {};//用于谬误筛选的对象;let _errorArg = [];//寄存错误信息的数组;
全局保护一个_errorMap,用于谬误筛选的对象,每当有谬误时,咱们依照约定好的规定,组成一个key,和_errorMap曾经存在的key进行比对,如果不存在,证实是新的谬误,须要上报,如果是曾经上报的谬误,就不再解决。
当然,为了避免_errorMap无限大、以及谬误漏报,当_errorMap的key的数量大于肯定数量时,咱们须要将_errorMap的key清空,这时候可能呈现后面曾经上报的谬误再次上报,然而不要紧,这个反复能够承受。
这个临界值能够依据理论状况定,我我的项目中最大值为100。
对于下面这个约定好的规定,其实就是依据咱们下面收集到的无关谬误的信息,组成的一个惟一key值,能实现唯一性且越短越好即可
//下面的代码,复制下来,不便看window.addEventListener("error", e => { if (!e) { return; } console.log(e.message);//错误信息 conosle.log(e.filename);//产生谬误的文件名 console.log(e.lineno);//产生谬误的行号(代码压缩合并对值有影响) console.log(e.colno);//产生谬误的列号(代码压缩合并对值有影响) const _target = e.target || e.srcElement; if (!_target) { return; } if (_target === window) { //语法错误 let _error = e.error; if (_error) { console.log(_error.stack);//谬误的堆栈信息 } } else { // 元素谬误,比方援用资源报错 let _src = _target.src; console.log(_src);//_src: 谬误的资源门路 }}, true);
对于语法错误,能够依据报错的文件名,行号,列号,组成keylet _key = `${e.filename}_${e.lineno}_${e.colno}`;对于资源加载谬误,能够依据谬误资源的门路作为key:let _key = e.src;
拿到key之后,咱们就能够存贮谬误了,
上面是存储的残缺代码:
function _sendErr(key, errType, errMsg) { //筛选 if (_ErrorMap.hasOwnProperty(key)) { //筛选到雷同的谬误,可将值加一,能够判断谬误呈现的次数 _ErrorMap[key] += 1; return; } //阈值 if (_ErrorArg.length >= 100) { return; } //存储谬误 //对于要发给后端的数据,可依据需要组织,数据结构 _ErrorArg.push({ errType: errType,//谬误类型 errMsg: errMsg || '',//错误信息 ver: _ver || '',//版本号 timestamp: new Date().getTime(),//工夫戳 }); //寄存错误信息的数组的阈值 if (Object.keys(_ErrorMap).length >= 100) { //达到阈值之后,清空去重对象 _ErrorMap = {}; } _ErrorMap[key] = 1; }
存储谬误的数组也须要阈值,理论使用中,咱们能够管制每次上报的谬误条数,然而,肯定得记得曾经上报的谬误肯定要从数组中移出。此外,上报的数据结构依据需要能够调整,个别蕴含错误信息、堆栈信息、加载失败资源的门路。
三、谬误上报
难道不是一收集到谬误就上报?
同时呈现一个两个谬误,当然能够立刻上报,
然而如果千百个谬误在短短的几秒钟呈现,就会呈现网络拥挤,甚至是程序解体。
因而,个别都会全局保护一个计时器,提早上报;
let _ErrorTimer = null;timerError();function timerError() { clearTimeout(_ErrorTimer); let _ErrorArg = g.IndexGlobal.ErrorArg;//后面提到的全局谬误存贮数组 let _ErrorArgLength = _ErrorArg.length; if (_ErrorArgLength > 0) { let _data = [];//要发送的错误信息,因为是一次性发5条,放零时数组中。 //组织要发送的错误信息 for (let i = 0; i < _ErrorArgLength; i++) { if (_data.length >= 5) { break; } _data.push(_ErrorArg.shift()); } if (_data.length) { //发送错误信息 //jq ajax g.IndexGlobal.errorSend(_data, function (p) { //失败 //如果发送失败,将未发送的数据,从新放入存储错误信息的数组中 if (p && p.data && p.data.data) { if (_ErrorArg.length >= 100) { return; } let _ag = p.data.data; try { g.IndexGlobal.ErrorArg.push(...JSON.parse(_ag)); } catch (error) { } } }); } } //计时器距离,当数组长度大于20时,一秒执行一次,默认2秒一次 let _ti = _ErrorArgLength >= 20 ? 1000 : 2000; _ErrorTimer = setTimeout(timerError, _ti);}
咱们能够依据谬误的数量,调整谬误上报的频率。然而这个距离个别不要太小,不然容易出问题。
四、注意事项
1.无论是window.addEventLister
还是console.error
,在咱们定义这些办法之前报的所有谬误,咱们是收集不到的,
怎么解决呢,很简略,js程序执行,咱们能够将相干代码放在最前头,
<!DOCTYPE html><html><script> //处理错误的代码 window.addEventLister; console.error = function(){}</script><head> <meta charset="utf-8"> <link rel="stylesheet" href=""> <link rel="stylesheet" href=""></head><body></body><script src="js/zepto.min.js"></script><script src="js/a.js"></script><script> //开始谬误上报计数器</script></html>
然而,要留神,放在最后面的是处理错误的逻辑,上报的计时器不能立刻开启,因为,此时jquery 还没加载,
计时器开启放在至多jquery加载实现之后。
2.肯定要做好处理错误局部代码的容错解决,不然业务逻辑代码还没报错,处理错误的局部反而报错就不好了。
3.当你间接双击html,在浏览器关上时,谬误收集机制可能不会正确工作,例如没有行号,列号,文件名,错误信息仅仅是Script Error
,这是因为onerror MDN
当加载自不同域的脚本中产生语法错误时,为防止信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简略的**"Script error."**
。在某些浏览器中,通过在<script>
应用`[crossorigin](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#attr-crossorigin)`
属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被笼罩。一个变通计划是独自解决"Script error.",告知谬误详情仅能通过浏览器控制台查看,无奈通过JavaScript拜访。
解决形式为:服务端增加Access-Control-Allow-Origin
,页面在script
标签中配置 crossorigin="anonymous"
。这样,便解决了因为跨域而带来的问题。
五、残缺代码
<!DOCTYPE html><html><script> //处理错误的命名空间 window['errorSpace'] = { ErrorTimer: null, //全局谬误上报计时器 ErrorArg: [], //全局谬误存储数组 ErrorMap: {}, //用于谬误筛选的对象 //存储错误信息 PushError: function (key, errMsg) { let _ErrorMap = window.errorSpace.ErrorMap; let _ErrorArg = window.errorSpace.ErrorArg; //筛选 if (_ErrorMap.hasOwnProperty(key)) { //筛选到雷同的谬误,可将值加一,能够判断谬误呈现的次数 _ErrorMap[key] += 1; return; } //阈值 if (_ErrorArg.length >= 100) { return; } //存储谬误 //对于要发给后端的数据,可依据需要组织,数据结构 _ErrorArg.push({ errMsg: errMsg || '', //错误信息 ver: '', //版本号 timestamp: new Date().getTime(), //工夫戳 }); //寄存错误信息的数组的阈值 if (Object.keys(_ErrorMap).length >= 100) { //达到阈值之后,清空去重对象 _ErrorMap = {}; } _ErrorMap[key] = 1; }, //谬误上报函数 ErrorSend: function () { clearTimeout(window.errorSpace.ErrorTimer); let _ErrorArg = window.errorSpace.ErrorArg; //后面提到的全局谬误存贮数组 let _ErrorArgLength = _ErrorArg.length; if (_ErrorArgLength > 0) { let _data = []; //要发送的错误信息,因为是一次性发5条,放零时数组中。 //组织要发送的错误信息 for (let i = 0; i < _ErrorArgLength; i++) { if (_data.length >= 5) { break; } _data.push(_ErrorArg.shift()); } if (_data.length) { //发送错误信息 //jq ajax var _d = { type: 'POST', url: '', data: _data || null, dataType: 'JSON', contentType: 'application/x-www-form-urlencoded', success: function (data, status, xhr) { //上报失败,将谬误从新存储 //这是假如服务端返回的数据结构是{status: 200} if (data.status !== 200) { //失败 try { //间接存入 //此处没有对_ErrorArg的长度进行判断,所以会溢出一次,使得谬误谬误尽可能的保留,问题不大,也能够不让溢出 _ErrorArg.push(..._data); } catch (error) { console.log(error); } } }, error: function (xhr, type, error) { //上报失败,将谬误从新存储 try { //间接存入 //此处没有对_ErrorArg的长度进行判断,所以会溢出一次,使得谬误谬误尽可能的保留,问题不大,也能够不让溢出 _ErrorArg.push(..._data); } catch (error) { console.log(error); } } } $.ajax(_d); } } //计时器距离,当数组长度大于20时,一秒执行一次,默认2秒一次 let _ti = _ErrorArgLength >= 20 ? 1000 : 2000; window.errorSpace.ErrorTimer = setTimeout(window.errorSpace.ErrorSend, _ti); }, }; //谬误收集 window.addEventListener("error", e => { if (!e) { return; } let _err_msg = ''; //要上报的错误信息 let _r = 0; //产生谬误的行号 let _l = 0; //产生谬误的列号 let _fileName = ''; //产生谬误的文件名 const srcElement = e.target || e.srcElement; if (!srcElement) { return; } if (srcElement === window) { //语法错误 let _error = e.error; if (_error) { _err_msg = _error.message + _error.stack; _r = e.lineno || 0; _l = e.colno || 0; _fileName = e.filename || ''; } } else { // 元素谬误,比方援用资源报错 if (srcElement.src) { _err_msg = srcElement.src; _fileName = srcElement.src; } } let _key = `${_fileName}_${_r}_${_l}`; window.errorSpace.PushError(_key, _err_msg); }, true); //解决console.error; let _windowError = window.console.error; window.console.error = function () { let _str = JSON.stringify(arguments); window.errorSpace.PushError(_str, _str); _windowError && _windowError.apply(window, arguments); }</script><head> <meta charset="utf-8"> <link rel="stylesheet" href=""> <link rel="stylesheet" href=""></head><body></body><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script> //开始谬误上报计数器 window.errorSpace && window.errorSpace.ErrorSend && window.errorSpace.ErrorSend(); </script></html>