给项目加上错误上报
Silencesnow opened this issue · 2 comments
起因
项目测试程度有限,就会有测不出的问题出现,上一篇我写了一个performance兼容性的问题,使我决定马上把全局错误上报加入到项目中,以下就是过程啦。
写法
全局错误上报可以利用window的error事件,搜到的例子都是写成window.onerror
,这是dom0级事件的写法,为了与项目其他事件监听统一,我想写成dom2级的,于是我写成这样:
window.addEventListener("error",function(e){
/* 上报内容 */
setTimeout(function(){
var msg={
errorMsg:e.error.stack,
position:'url:'+document.URL+'l:'+e.lineno+',c:'+e.colno,
name:'{{postReportMsg}}'||'{{postReporInnerMsg}}'||'{{postReportMsgOut}}'||'OUTPAGE',
us:navigator.userAgent
};
var xhr=new XMLHttpRequest();
xhr.open('POST','/ajax/errorReport',true);
xhr.setRequestHeader('Content-Type','application/json');
xhr.send(JSON.stringify(msg));
xhr.onload=function(e){
if(this.status==200){
console.log('error report success',this.responseText)
}
}
xhr.onerror = function(){
console.log('error report fail');
};
},0);
},true);
调试时发现window.onerror和addEventListener二者传入的参数不太一致,不过不影响关键数据,于是我愉快的部署了。
第二天早上一上班,打开后台的统计列表,我惊呆了,一夜间上万条的错误上报是什么鬼!
分析上报内容
值得关注的奇怪报错——TypeError: Cannot read property 'stack' of undefined at 某位置
——这个位置指向错误上报的方法,看起来某些报错的error对象没有stack属性,此时可以注意到,window.onerror和window.addEventListener('error')并不相同,引用mdn上的一段话:
-
When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface
ErrorEvent
is fired at window and window.onerror() is invoked (as well as handlers attached by window.addEventListener (not only capturing)). -
When a resource (such as an
img
orscript
) fails to load, an error event using interfaceEvent
is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but (at least in Firefox) can be handled with a single capturing window.addEventListener.
总结一下是:js报错产生一个ErrorEvent,使用接口'ErrorEvent',触发对象是window,window.onerror和window.addEventListener都会被触发。
而图片或脚本资源加载失败,会产生一个error Event,这个使用接口'Event',触发对象是加载失败的元素,这个事件不冒泡,但是可以捕获。
图片或脚本资源加载失败,最常见的情况就是懒加载图片时,我们写的src="#",这就会触发一个图片加载失败的error Event
由于我部署的window.addEventListener('error',function(e){},true)设为了捕获阶段触发事件监听函数,导致图片加载失败等事件也被搜集了进来,而图片加载失败触发的接口不同于脚本错误,一个是error Event,使用Event接口,一个是ErrorEvent,使用ErrorEvent接口,前者没有error属性,就更不会在error属性上有stack属性了,所以才会报错。
修正和兼容性
是时候看一下完整的参数和兼容性了:原文地址
历史原因,window.onerror有5个参数;window.addEventListener('error',function(e){})有一个参数error,该参数有5个属性,对应于onerror的5个参数。
这5个参数兼容性参差不齐,现代浏览器都支持的是前三个,报错信息
,报错位置
,行号
,后两个列号
,error对象
主要是safari系不支持,然而由于现在js普遍都是打包过的,你会发现,报错的位置行号经常都是1,列号是1万几,丢失列号很难忍受,error对象
中也有很有价值的堆栈信息,应该做到尽量多的搜集。参考这张兼容性表格出处的文章,建议的最完善的办法是加try-catch,可以catch到很全面的报错信息,不过如果考虑简单的错误搜集,并且你主要指向移动端的话,也可以只使用window.onerror。
需要注意的是,错误上报方法需要放在头部,js脚本按顺序执行,未执行该方法就出现报错,错误无法被收集到
。
还有一个tips,普遍推荐的方式都是加一层setTimeout(function(){},0)以实现不阻塞上报。
以下是简单error上报的正确打开方式:
/* 第一种 */
/* 我是头部 */
window.onerror=function(e,url,l,c,error){
setTimeout(function(){
var msg={
errorMsg:e,
position:'url:'+document.URL+',file:'+url+',l:'+l+',c:'+c+',error:'+error,
name:页面标识,
us:navigator.userAgent
};
var xhr=new XMLHttpRequest();
xhr.open('POST','错误上报接口',true);
xhr.setRequestHeader('Content-Type','application/json');
xhr.send(JSON.stringify(msg));
xhr.onload=function(e){
if(this.status==200){
console.log('error report success',this.responseText)
}
}
xhr.onerror = function(){
console.log('error report fail');
};
},0)
}
/* 第二种 */
window.addEventListener('error',function(e){
/* setTimeout方法同上,只不过参数为e.message,e.filename,e.lineno,e.colno,e.error */
})
遇到的常见错误
-
Uncaught ReferenceError: WeixinJSBridge is not defined
微信环境经典报错, 微信内置浏览器会有WeixinJSBridge,但是需要一定的加载时间,如果在它加载完成之前进行微信接口操作,就会出现这个bug。避免方法是监听它的加载事件:
if (typeof window.WeixinJSBridge == "undefined"){ document.addEventListener("WeixinJSBridgeReady", function () { /* 操作 */ } }else { /* 操作 */ }
-
Script error
外部脚本报错,最常见就是http页面被运营商劫持插入广告。
此外,想要获取外部脚本报错详情信息,需要在
<script>
标签处加一个crossorigin属性,并将拉取外部脚本的服务器资源设为'Access-Control-Allow-Origin:*'
,这里有个具体参考操作。 -
Uncaught ReferenceError: FastClick is not defined
Uncaught ReferenceError: jQuery is not defined
ReferenceError: Can't find variable: wx
都是由于资源未加载完成就使用导致的。
微信Uncaught ReferenceError: WeixinJSBridge is not defined那个处理没太用吧?第一次调起支付的时候还是有Uncaught ReferenceError: WeixinJSBridge is not defined这个问题,非第一次支付调起才没有问题
请问这个问题您解决了吗?