在没风的地方找太阳  在你冷的地方做暖阳 人事纷纷  你总太天真  往后的余生  我只要你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你 想带你去看晴空万里  想大声告诉你我为你着迷 往事匆匆  你总会被感动  往后的余生  我只要你 往后余生  冬雪是你  春花是你  夏雨也是你 秋黄是你  四季冷暖是你  目光所致  也是你 往后余生  风雪是你  平淡是你  清贫也是你 荣华是你  心底温柔是你  目光所致  也是你
jQuery火箭图标返回顶部代码 - 站长素材

聊聊前端监控——错误监控篇

当有人问起:你们的公司的这款应用用户体验怎么样呀?访问量怎么样?此时,你该怎么回答呢?你会回答:UV、PV 巴拉巴拉,秒开率、FP、TTI 巴拉巴拉。

那么,这些数据是哪里来的呢?显而易见,这些数据都来自前端监控系统。

前端监控的意义

当今时代,是一个快节奏的时代,应用的性能极大影响着用户的留存率,没有用户会忍受一个卡到爆的应用。而监控应用性能的重担,就由前端监控系统肩负着。

其次,对于线上应用来说,故障是不可避免的,对于高日活的应用来说,每次故障都意味着大量的损失。试想,如果是淘宝挂了一天,那么损失是多么惨痛。所以,对于开发人员来说,必须要尽早发现线上故障,而不是等到客户打爆客服的电话才发现。线上错误监控,也是前端监控的任务之一。

最后,作为商业公司,需要根据用户行为和数据进行分析,进一步制定各种策略,如果没有各种数据,那么 BI 会热情的找你谈谈人生。而这些数据,也是前端监控系统获取的。

总而言之,前端监控肩负着:性能监控、错误监控以及数据上报等功能,无论对于大公司还是小公司,可以说是必不可缺的了。

今天,我们先来聊聊前端监控中的错误监控。

错误监控概述

一般来说,按照错误监控错误监控可以分为:脚本错误监控、请求错误监控以及资源错误监控。

脚本错误监控

脚本错误大体可以分为两种:编译时错误以及运行时错误。其中,编译时错误一般在开发阶段就会发现,配合 lint 工具比如 eslint、tslint 等以及 git 提交插件比如 husky 等,基本可以保证线上代码不出现低级的编译时错误。大厂一般都有发布前置检测平台,能够在发布前提前发现编译时错误,当然,原理依然和之前所说的类似。

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

说到脚本错误监控,你想到的第一个是什么?对,就是 try catch !

在编写 JavaScript 时,我们为了防止出现错误阻塞程序,我们会通过 try catch 捕获错误,对于错误捕获,这是最简单也是最通用的方案。

但是,try catch 捕获错误是侵入式的,需要在开发代码时即提前进行处理,而作为一个监控系统,无法做到在所有可能产生错误的代码片段中都嵌入 try catch。所以,我们需要全局捕获脚本错误。

常规脚本错误

当页面出现脚本错误时,就会产生 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 即可
};

可以发现,各种错误监控所需的信息,如错误信息、错误源文件的 URL、错误行号、错误列号都被回调函数所传入。

但是,window.onerror 有两个缺点:

  1. 只能绑定一个回调函数,如果想在不同文件中想绑定不同的回调函数,window.onerror 显然无法完成;同时,不同回调函数直接容易造成互相覆盖。
  2. 回调函数的参数过于离散,使用不方便

所以,一般情况下,我们使用 addEventListener 来代替。

/**
 * @param event 事件名
 * @param function 回调函数
 * @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行
 */
window.addEventListener('error', (event) => {
  // addEventListener 回调函数的离散参数全部聚合在 error 对象中
  // 上报错误
}, true)

tips:在一些特殊情况下,我们依然需要使用 window.onerror。比如,不期望在控制台抛出错误时,因为只有 window.onerror 才能阻止抛出错误到控制台

Promise 错误

使用了这两种方法,是不是可以捕获所有脚本错误了呢?这个问题再几年前其实是正确的,但是随着前端技术的发展,出现了 Promise 这项技术,而使用这两种常规方法无法捕获 Promise 错误。

和常规脚本错误的捕获一样,我们只需捕获 Promise 对应的错误事件即可。而 Promise 错误事件有两种,unhandledrejection 以及 rejectionhandled

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件。

当 Promise 被 reject 且有 reject 处理器的时候,会触发 rejectionhandled 事件。

// unhandledrejection 推荐处理方案
window.addEventListener('unhandledrejection', (event) => {
  console.log(event)
}, true);

// unhandledrejection 备选处理方案
window.onunhandledrejection = function (error) {
  console.log(error)
}

// rejectionhandled 推荐处理方案
window.addEventListener('rejectionhandled', (event) => {
  console.log(event)
}, true);

// rejectionhandled 备选处理方案
window.onrejectionhandled = function (error) {
  console.log(error)
}

框架错误

由于我 React 使用的不多,所以在此只讨论下 Vue 的框架错误处理,如果有大佬了解 React 的框架错误处理,欢迎补充~

在 Vue 中,框架提供了 errorHandler 这个 API 来捕获并处理错误。

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

值得一提的是,框架错误指的不是框架层面的错误,而是指框架提供了 API 来捕获全局错误。

请求错误监控

一般来说,前端请求有两种方案,使用 ajax 或者 fetch ,所以只需重写两种方法,进行代理,即可实现请求错误监控。

代理的核心在于使用 apply 重新执行原有方法,并且在执行原有方法之前进行监听操作。在请求错误监控中,我们关心三种错误事件:abort,error 以及 timeout,所以,只需在代理中对这三种事件进行统一处理即可。

tips:如果能够统一使用一种请求工具,如 axios 等,那么不需要重写 ajax 或者 fetch 只需在请求拦截器以及响应拦截器进行处理上报即可

资源错误监控

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

那么如果区分脚本错误还是资源错误呢?我们可以通过 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);

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

补充:跨域脚本错误捕获

为了性能方面的考虑,我们一般会将脚本文件放到 CDN ,这种方法会大大加快首屏时间。但是,如果脚本报错,此时,浏览器出于于安全方面的考虑,对于不同源的脚本报错,无法捕获到详细错误信息,只会显示 Script Error。那么,有解决该问题的方案吗?

  1. 方案一:所有的脚本全部放到同一源下,但是,该方案会放弃 CDN ,降低性能。
  2. 方案二:在 script 标签中,添加 crossorigin 属性(推荐使用 webpack 插件自动添加);同时,配置 CDN 服务器,为跨域脚本配上 CORS

可以发现,方案二基本可以完美解决跨域脚本错误捕获的问题。但是,其实该方案有一个隐藏的坑,即兼容性问题,crossorigin 属性对于 IE 以及 Safari 支持程度不高。

所以,该如何真正完美的解决跨域脚本错误捕获问题?

终极解决方案:对所有原生方法进行代理~

但是,一方面,很难覆盖所有的原生方法,另一方面,对原生方法进行代理容易出现无法预知的问题。

综合所有方案,看起来还是方案二最靠谱,至于低级浏览器,就让它们随风消逝吧~

posted @ 2022-03-28 23:31  艺术诗人  阅读(942)  评论(0编辑  收藏  举报