前端异常监控

 

目的:及时感知线上问题,减少异常对用户的影响。

在JS中,异常不会导致JS线程崩溃,最多只会让当前执行程序提前终止。

  1. 当前代码块将作为一个任务压入任务队列中,JS 线程会不断地从任务队列中提取任务执行。

  2. 当任务执行过程中出现异常,且异常没有捕获处理,则会一直沿着调用栈一层层向外抛出,最终终止当前任务的执行。

  3. JS 线程会继续从任务队列中提取下一个任务继续执行。

错误监控的分类:

a. 脚本错误监控

b. 请求错误监控

c. 资源错误监控

脚本错误

脚本错误,可分为编译时的错误和运行时错误。编译时错误,可以通过lint(如eslint和tslint)以及git提交插件husky,基本保障提交到线上的代码不出现低级的编译时错误。

而发现并上报运行时错误就是前端检测平台的本质工作啦,一般来说,脚本错误监控指的就是运行时错误监控。

1. try... catch... 

说到脚本错误监控,我们可能最先想到的是try ... catch ... 。try... catch... 语法块确实可以帮助我们帮助我们捕获异常,但仅仅是非异步的错误;并且这种捕获是侵入式的,仅限于可预见的异常。要对这个系统进行异常监控,这样方法并非最佳选择,我们需要全局的异常捕获。

如果要在异步中使用try... catch... ,则要把try... catch... 发在异步的内部

setTimeout(() => {
    try {
        error;
    } catch (e) {
        console.log('error捕获', e);
    }
}, 1000);

  

2. onerror事件(冒泡阶段)

当页面出现脚本错误时,就会产生 onerror 事件,我们只需捕获该事件即可。这种捕获方法可以捕获到同步和异步错误,并且这种捕获的错误不再是可预见的。

 * @description window.onerror 全局捕获错误
 * @param event 错误信息,如果是
 * @param source 错误源文件URL
 * @param lineno 行号
 * @param colno 列号
 * @param error Error对象
 */
window.onerror = function (event, source, lineno, colno, error) {
  // 上报错误
  // 如果不想在控制台抛出错误,只需返回 true 即可
  console.log('我知道错误了');
  console.log({ msg, url, row, col, error })
  return true;
};

 需要注意的是,

  • window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出,否则即使知道异常的发生,控制台还是会显示。
  • 为了达到全局捕获,我们通常将onerror定义在所有JS 脚本的前面。(如果写在后面,一旦发生错误的话是不会被 onerror 捕获到的。)
  • onerror无法捕获到网络异常。
3. addEventListener
/**
 * @param event 事件名
 * @param function 回调函数
 * @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行
 */
window.addEventListener('error', (event) => {
  // addEventListener 回调函数的离散参数全部聚合在 error 对象中
  // 上报错误
}, true)

相比于onerror, addEventListener可以绑定多个回调函数.此外还可以指定错误捕获的时机是冒泡阶段(默认false)还是捕获阶段(true)

4. Promise错误捕获:上述方法(try catch, onerror, addEventListener)都无法捕获Promise异常。

对于未处理的Promise异常,会触发unhandledrejection事件。

对于处理过的Promise异常,会触发rejectionhandled事件。

window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我感知不到 promise 错误');
  console.log(
    msg, url, row, col, error
  );
}, true);
Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
})

所以官方总是建议我们在Promise的最后加上catch(错误被捕获,不会使程序终止)

new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
}).catch((error) => {
    console.log(error);
})

如果应用中用到很多的 Promise 实例,特别是你在一些基于 promise 的异步库(比如 axios 等)一定要小心,因为你不知道什么时候这些异步请求会抛出异常而你并没有处理它,所以你最好添加一个 Promise 全局异常捕获事件 unhandledrejection。

请求错误

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

资源错误

资源错误监控本质上和常规脚本错误监控一样,都是监控错误事件实现错误捕获。

那么如果区分脚本错误还是资源错误呢?我们可以通过 instanceof 区分,脚本错误参数对象 instanceof ErrorEvent,而资源错误的参数对象 instanceof Event

由于 ErrorEvent 继承于 Event ,所以不管是脚本错误还是资源错误的参数对象,它们都 instanceof Event,所以,需要先判断脚本错误。

此外,两个参数对象之间有一些细微的不同,比如,脚本错误的参数对象中包含 message ,而资源错误没有,这些都可以作为判断资源错误或者脚本错误的依据。

/**
 * @param event 事件名
 * @param function 回调函数
 * @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行
 */
window.addEventListener('error', (event) => {
  if (event instanceof ErrorEvent) {
    console.log('脚本错误')
  } else if (event instanceof Event) {
    console.log('资源错误')
  }
}, true);

使用 addEventListener 捕获资源错误时,一定要将 useCapture 即第三个选项设为 true,因为资源错误没有冒泡,所以只能在捕获阶段捕获。同理,由于 window.onerror 是通过在冒泡阶段捕获错误,所以无法捕获资源错误。

参考文献:
https://juejin.cn/post/6867773840768909326#heading-6
https://juejin.cn/post/6896334879051481101
https://juejin.cn/post/6896657931270373389#heading-12
posted @ 2021-01-24 21:16  cecelia  阅读(220)  评论(0)    收藏  举报