前端错误捕获

常见错误的分类javascript

对于用户在访问页面时发生的错误,主要包括如下几个类型:css

一、js运行时错误html

JavaScript代码在用户浏览器中执行时,因为一些边界状况、本地环境的不可控等因素,可能会存在js运行时错误。前端

而依赖客户端的某些方法,因为兼容性或者网络等问题,也有几率会出现运行时错误。vue

e.g: 下图是当使用了未定义的变量"foo",致使产生js运行时错误时的上报数据:java



二、资源加载错误react

这里的静态资源包括js、css以及image等。如今的web项目,每每依赖了大量的静态资源,并且通常也会有cdn存在。ios

若是某个节点出现问题致使某个静态资源没法访问,就须要可以捕获这种异常并进行上报,方便第一时间解决问题。git

e.g: 下图是图片资源不存在时的上报数据:github


三、未处理的promise错误

未使用catch捕获的promise错误,每每都会存在比较大的风险。而编码时有可能覆盖的不够全面,所以有必要监控未处理的promise错误并进行上报。

e.g: 下图是promise请求接口发生错误后,未进行catch时的上报数据:


四、异步请求错误(fetch与xhr)

异步错误的捕获分为两个部分:一个是传统的XMLHttpRequest,另外一个是使用fetch api。

像axios和jQuery等库就是在xhr上的封装,而有些状况也可能会使用原生的fetch,所以对这两种状况都要进行捕获。

e.g: 下图是xhr请求接口返回400时捕获后的上报数据:



各个类型错误的捕获方式

一、window.onerror与window.addEventListener('error')捕获js运行时错误

使用window.onerror和window.addEventListener('error')都能捕获,可是window.onerror含有详细的error堆栈信息,存在error.stack中,因此咱们选择使用onerror的方式对js运行时错误进行捕获。

window.onerror =function(msg, url, lineNo, columnNo, error){// 处理错误信息}// demomsg: UncaughtTypeError: UncaughtReferenceError: a is not definederror.statck:TypeError:ReferenceError: a is not defined at http://xxxx.js:1:13window.addEventListener('error', event => (){// 处理错误信息},false);// true表明在捕获阶段调用,false表明在冒泡阶段捕获。使用true或false均可以,默认为false

二、资源加载错误使用addEventListener去监听error事件捕获

实现原理:当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。

这些error事件不会向上冒泡到window,不过能被window.addEventListener在捕获阶段捕获。

但这里须要注意,因为上面提到了addEventListener也可以捕获js错误,所以须要过滤避免重复上报,判断为资源错误的时候才进行上报。

window.addEventListener('error', event => (){// 过滤js errorlettarget = event.target || event.srcElement;letisElementTarget = targetinstanceofHTMLScriptElement || targetinstanceofHTMLLinkElement || targetinstanceofHTMLImageElement;if(!isElementTarget)returnfalse;// 上报资源地址leturl = target.src || target.href;console.log(url);},true);

三、未处理的promise错误处理方式

实现原理:当promise被reject而且错误信息没有被处理的时候,会抛出一个unhandledrejection。

这个错误不会被window.onerror以及window.addEventListener('error')捕获,可是有专门的window.addEventListener('unhandledrejection')方法进行捕获处理。

window.addEventListener('rejectionhandled', event => {// 错误的详细信息在reason字段// demo:settimeout errorconsole.log(event.reason);});

四、fetch与xhr错误的捕获

对于fetch和xhr,咱们须要经过改写它们的原生方法,在触发错误时进行自动化的捕获和上报。

改写fetch方法:

// fetch的处理function_errorFetchInit(){if(!window.fetch)return;let_oldFetch =window.fetch;window.fetch =function(){return_oldFetch.apply(this,arguments)        .then(res=>{if(!res.ok) {// 当status不为2XX的时候,上报错误}returnres;        })// 当fetch方法错误时上报.catch(error=>{// error.message,// error.stack// 抛出错误而且上报throwerror;        })    }}

对于XMLHttpRequest的重写:

xhr改写

// xhr的处理function_errorAjaxInit(){letprotocol =window.location.protocol;if(protocol ==='file:')return;// 处理XMLHttpRequestif(!window.XMLHttpRequest) {return;      }letxmlhttp =window.XMLHttpRequest;// 保存原生send方法let_oldSend = xmlhttp.prototype.send;let_handleEvent =function(event){try{if(event && event.currentTarget && event.currentTarget.status !==200) {// event.currentTarget 即为构建的xhr实例// event.currentTarget.response// event.currentTarget.responseURL || event.currentTarget.ajaxUrl// event.currentTarget.status// event.currentTarget.statusText});            }        }catch(e) {vaconsole.log('Tool\'s error: '+ e);        }    }    xmlhttp.prototype.send =function(){this.addEventListener('error', _handleEvent);// 失败this.addEventListener('load', _handleEvent);// 完成this.addEventListener('abort', _handleEvent);// 取消return_oldSend.apply(this,arguments);    }}

关于responseURL 的说明

须要特别注意的是,当请求彻底没法执行的时候,XMLHttpRequest会收到status=0 和 statusText=null的返回,此时responseURL也为空string。

另外在安卓4.4及如下版本的webview中,xhr对象也不存在responseURL属性。

所以咱们须要额外的改写xhr的open方法,将传入的url记录下来,方便上报时带上。

var_oldOpen = xmlhttp.prototype.open;// 重写open方法,记录请求的urlxmlhttp.prototype.open =function(method, url){    _oldOpen.apply(this,arguments);this.ajaxUrl = url;};

其余问题

一、其余框架,例如vue项目的错误捕获

vue内部发生的错误会被Vue拦截,所以vue提供方法给咱们处理vue组件内部发生的错误。

Vue.config.errorHandler =function(err, vm, info){// handle error// `info` 是 Vue 特定的错误信息,好比错误所在的生命周期钩子// 只在 2.2.0+ 可用}

二、script error的解决方式

"script error.”有时也被称为跨域错误。当网站请求并执行一个托管在第三方域名下的脚本时,就可能遇到该错误。最多见的情形是使用 CDN 托管 JS 资源。

其实这并非一个 JavaScript Bug。出于安全考虑,浏览器会刻意隐藏其余域的 JS 文件抛出的具体错误信息,这样作能够有效避免敏感信息无心中被不受控制的第三方脚本捕获。

所以,浏览器只容许同域下的脚本捕获具体错误信息,而其余脚本只知道发生了一个错误,但没法获知错误的具体内容。

解决方案1:(推荐)

添加 crossorigin="anonymous" 属性。

此步骤的做用是告知浏览器以匿名方式获取目标脚本。这意味着请求脚本时不会向服务端发送潜在的用户身份信息(例如 Cookies、HTTP 证书等)。

添加跨域 HTTP 响应头:

Access-Control-Allow-Origin: *

或者

Access-Control-Allow-Origin: http://test.com

注意:大部分主流 CDN 默认添加了 Access-Control-Allow-Origin 属性。

完成上述两步以后,便可经过 window.onerror 捕获跨域脚本的报错信息。

解决方案2

难以在 HTTP 请求响应头中添加跨域属性时,还能够考虑 try catch 这个备选方案。

在以下示例 HTML 页面中加入 try catch:

<!doctype html>Test page in http://test.com// app.js里面有一个foo方法,调用了不存在的bar方法window.onerror =function(message, url, line, column, error){console.log(message, url, line, column, error);    }try{        foo();    }catch(e) {console.log(e);throwe;    }// 运行输出结果以下:=> ReferenceError: bar is not definedat foo (http://another-domain.com/app.js:2:3)at http://test.com/:15:3=> "Script error.", "", 0, 0, undefined

可见 try catch 中的 Console 语句输出了完整的信息,但 window.onerror 中只能捕获“Script error”。根据这个特色,能够在 catch 语句中手动上报捕获的异常。

总结

上述的错误捕获基本覆盖了前端监控所需的错误场景,可是第三部分指出的两个其余问题,目前解决的方式都不太完美。

对于有使用框架的项目:一是须要有额外的处理流程,好比示例中就须要单独为vue项目进行初始化;二是对于其余框架,都须要单独处理,例如react项目的话,则须要使用官方提供的componentDidCatch方法来作错误捕获。

而对于跨域js捕获的问题:咱们并不能保证全部的跨域静态资源都添加跨域 HTTP 响应头;而经过第二种包裹try-catch的方式进行上报,则须要考虑的场景繁多而且没法保证没有遗漏。

虽然存在这两点不足,但前端错误捕获这部分仍是和项目的使用场景密切相关的。咱们能够在了解这些方式之后,选择最适合本身项目的方案,为本身的监控工具服务。

—— —— 参考文档 —— ——

1.Using XMLHttpRequest: 

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest

2.script error 产生的缘由和解决办法: 

https://www.alibabacloud.com/help/zh/faq-detail/88579.htm

3.JavaScript执行错误: 

https://docs.fundebug.com/notifier/javascript/type/javascript.html

4.betterjs的script error: 

https://github.com/BetterJS/badjs-report/issues/3

5.Vuejs的errorHandler: 

https://cn.vuejs.org/v2/api/index.html#errorHandler

6.React的componentDidCatch: 

https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html