如何处理前端异常监控?

为什么要进行异常处理?

很多异常是不可控的,比如资源加载异常,ajax请求异常等,会影响最终的呈现效果,做好异常处理,有大致以下几点好处:

  • 1.增强用户体验;
  • 2.快速定位问题原因,及时发现问题。特别是移动端,机型、系统等不一样,有了异常处理并上报,定位快;
  • 3.完善前端监控系统方案。

需要处理哪些异常?

  • JS语法错误、代码异常
  • ajax请求异常
  • 静态资源加载异常
  • promise异常
  • iframe异常
  • 跨域 script error
  • 崩溃和卡顿

 异常处理的方式

try-catch

 try-catch只能捕获到同步运行时错误,无法捕获语法错误和异步错误。

示例:运行时错误(能捕获)

try {
    error;
} catch(e) {
    console.log('捕获到错误了');
    console.log(e);
}

 示例:语法错误(不能捕获)

try {
    var error = 'err;  // 少一个单引号
} catch(e) {
    console.log('捕获不到错误了');
    console.log(e);
}

上面红色标记的错误大致意思为:无效或者意外的标记。但是这种语法错误会直接抛出来,使后面的程序代码无法运行,直接崩溃,一般在编码的时候就能发现这类错误。

示例:异步错误(不能捕获)

try {
    setTimeout(function() {
        error; // 异步错误,没有定义
    })
} catch(e) {
    console.log('捕获不到错误了');
    console.log(e);
}

window.onerror

 当JS运行发生错误时,window会触发一个errorEvent接口的error事件并执行window.onerror()。

window.onerror比try-catch强一些,在try-catch的基础上,它可以捕获异步错误。

/**
 * @param {String} message 错误信息
 * @param {String} resource 出错文件
 * @param {Number} row 行号
 * @param {Number} col 列号
 * @param {Object} error 错误详细信息error对象
 * */
window.onerror = function(message,resource,row,col,error) {
    console.log('捕获到错误信息');
    console.log({message,resource,row,col,error});
    return true;
}

示例:异步错误(能捕获)

setTimeout(function() {
    error; // 异步错误,没有定义
})

注意:

1.window.onerror也是不能捕获语法错误的;

2.window.onerror也不能捕获网络请求异常情况,如静态资源异常、接口异常等都是不行的;

3.特别注意的是,window.onerror函数在返回true的时候,异常才不会向上抛出,否则,控制台还是会显示Uncaught Error: xxxxx

如下示例,我们让window.onerror函数没有返回true.

/**
 * @param {String} message 错误信息
 * @param {String} resource 出错文件
 * @param {Number} row 行号
 * @param {Number} col 列号
 * @param {Object} error 错误详细信息error对象
 * */
window.onerror = function(message,resource,row,col,error) {
    console.log('捕获到错误信息');
    console.log({message,resource,row,col,error});
}
    
setTimeout(function() {
    error; // 异步错误,没有定义
})

 小结:从上面两种捕获错误的方式来看,window.onerror()函数主要用来捕获意料之外的错误,而try-catch主要是捕获可预见情况下的特定错误。

window.addEventListener

 window.onerror函数不能捕获静态资源加载失败的异常情况,当资源(图片或脚本)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数,这些error事件不会向上冒泡到window上,但是可以被window.addEventListener捕获。

<script type="text/javascript">
window.addEventListener('error',function(error){
    console.log('捕获到异常错误了');
    console.log(error);
},true)
</script>
<img src="./images/error.jpg"/>

由于网络请求异常不会事件冒泡,因此需要在捕获阶段将其捕捉到才行,这种方式虽然可以捕获到网络请求异常,但是无法判断HTTP的状态码是404还是其他的如500等,所有还需要配合服务端日志进行排查分析才可以。

 unhandledrejection监听UnCaught Promise Error

在很多时候我们使用Promise的时候忘记了写catch,那么可以在全局增加一个unhandledrejection的监听,用来全局监听UnCaught Promise Error,使用方式如下:

window.addEventListener("unhandledrejection", function(e){
  e.preventDefault(); // 去掉控制台的异常显示
  console.log('捕获到异常:', e);
  return true;
});
Promise.reject('promise error');

VUE errorHandler

Vue.config.errorHandler = (err, vm, info) => {
  console.error('通过vue errorHandler捕获的错误');
  console.error(err);
  console.error(vm);
  console.error(info);
}

如果在组件渲染时出现运行错误,错误将会被传递至全局 Vue.config.errorHandler 配置函数 (如果已设置)。利用这个钩子函数来配合错误跟踪服务是个不错的主意。比如 Sentry,它为 Vue 提供了官方集成

iframe异常

iframe的异常捕获需要借助window.onerror:

<iframe src="./a.html" frameborder="0"></iframe>
<script type="text/javascript">
/**
 * @param {String} message 错误信息
 * @param {String} resource 出错文件
 * @param {Number} row 行号
 * @param {Number} col 列号
 * @param {Object} error 错误详细信息error对象
 * */
window.frames[0].onerror = function(message,resource,row,col,error) {
    console.log('捕获到错误信息');
    console.log({message,resource,row,col,error});
    return true;
}
</script>

我在a.html中添加了一句JS代码如下:

<script type="text/javascript">
    var a= '1; // 缺少引号
</script>

那么在父窗口捕获到的错误是:

Script error

 出现Script error的情况,基本上是跨域问题。例如我们的工程中的静态资源使用CDN,我们引入的CDN方式可能是有不同的域名,如果没有进行额外的配置,就会出现Script error。

解决思路:跨源资源共享机制(CORS),为 script 标签添加 crossOrigin 属性:

<script src="http://localhost:8081/index.js" crossorigin></script>

或者动态去添加 js 脚本:

const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);

特别注意,服务器端需要设置:Access-Control-Allow-Origin

解决 Script Error 的另类思路:改写 EventTarget 的 addEventListener 方法;对传入的 listener 进行包装,返回包装过的 listener,对其执行进行 try-catch;浏览器不会对 try-catch 起来的异常进行跨域拦截,所以 catch 到的时候,是有堆栈信息的;重新 throw 出来异常的时候,执行的是同域代码,所以 window.onerror 捕获的时候不会丢失堆栈信息;利用包装 addEventListener,我们还可以达到「扩展堆栈」的效果:

(() => {
   const originAddEventListener = EventTarget.prototype.addEventListener;
   EventTarget.prototype.addEventListener = function (type, listener, options) {
   // 捕获添加事件时的堆栈
   const addStack = new Error(`Event (${type})`).stack;
   const wrappedListener = function (...args) {
       try {
         return listener.apply(this, args);
       }
       catch (err) {
        // 异常发生时,扩展堆栈
        err.stack += '\n' + addStack;
         throw err;
       }
     }
     return originAddEventListener.call(this, type, wrappedListener, options);
   }
})()

 崩溃和卡顿

卡顿也就是网页暂时响应比较慢, JS 可能无法及时执行。但是网页崩溃可以利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。

window.addEventListener('load', function () {
    sessionStorage.setItem('good_exit', 'pending');
    setInterval(function () {
        sessionStorage.setItem('time_before_crash', new Date().toString());
    }, 1000);
  });

  window.addEventListener('beforeunload', function () {
    sessionStorage.setItem('good_exit', 'true');
  });

  if(sessionStorage.getItem('good_exit') &&
    sessionStorage.getItem('good_exit') !== 'true') {
    /*
        insert crash logging code here
    */
    alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
  }

错误上报

 通过 Ajax 发送数据 因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。

动态创建 img 标签的形式:

function report(error) {
  let reportUrl = 'http://jartto.wang/report';
  new Image().src = `${reportUrl}?logs=${error}`;
}

收集异常信息量太多,怎么办?实际中,我们不得不考虑这样一种情况:如果你的网站访问量很大,那么一个必然的错误发送的信息就有很多条,这时候,我们需要设置采集率,从而减缓服务器的压力:

Reporter.send = function(data) {
  // 只采集 30%
  if(Math.random() < 0.3) {
    send(data)      // 上报错误信息
  }
}

采集率应该通过实际情况来设定,随机数,或者某些用户特征都是不错的选择。

也可以通过Sentry来实现前端的异常监控及上报。

总结

  • 可疑区域增加 Try-Catch
  • 全局监控 JS 异常 window.onerror
  • 全局监控静态资源异常 window.addEventListener
  • 捕获没有 Catch 的 Promise 异常:unhandledrejection
  • VUE errorHandler
  • 监控网页崩溃:window 对象的 load 和 beforeunload
  • 跨域 crossOrigin 解决

参考地址

posted @ 2021-05-14 22:45  风雨后见彩虹  阅读(988)  评论(0编辑  收藏  举报