当有人问起:你们的公司的这款应用用户体验怎么样呀?访问量怎么样?此时,你该怎么回答呢?你会回答:UV(独立访客数,以cookie或token为依据)、PV(访问量,页面浏览次数) ,IP(独立IP数)等页面流量指标。
秒开率、RTT(延迟,数据往返时间)、TTI(页面可交互时间,用户与页面进行互动之前所花费的时间) 、FCP(首屏时间)、 FP(白屏时间)等性能指标。详见:前端页面的常见性能指标
那么,这些数据是哪里来的呢?显而易见,这些数据都来自前端监控系统。
前端监控的意义
- 性能监控,避免由性能问题影响用户的留存率
- 错误监控,避免用户流失
- 数据上报,根据用户行为和数据进行分析
错误采集方式
错误监控可以分为:脚本错误监控、请求错误监控、资源错误监控。
脚本错误监控
编译时错误
一般在开发阶段就会发现,配合 lint 工具比如 eslint、tslint 等以及 git 提交插件比如 husky 等,基本可以保证线上代码不出现低级的编译时错误。大厂一般都有发布前置检测平台,能够在发布前提前发现编译时错误。
运行时错误
搭建前端检测平台。
前端检测平台错误捕获的机制有:
- Js 通过
try catch
捕获错误。但是,try catch
捕获错误是侵入式的,需要在开发代码时即提前进行处理,无法做到在所有可能产生错误的代码片段中都嵌入try catch
- 全局捕获脚本错误:根据
window.onerror
事件进行全局捕获,但只能绑定一个回调函数,且回调函数的参数过于离散,使用不方便,且不能监听到资源错误。可以使用addEventListener
,但只有window.onerror
才能阻止抛出错误到控制台 - 以上两种方法无法捕获
Promise
错误,需要用到Promise
错误事件:unhandledrejection
以及rejectionhandled
。
try-catch缺点:
- 需要提前进行处理,无法全局捕获
- 通过
try catch
包裹,影响代码可读性 - 无法处理语法错误,比如使用了中文标点
- 无法处理异步中的错误,比如
setTimeout
/**
* @description window.onerror 全局捕获错误
* @param event 错误信息,如果是
* @param source 错误源文件URL
* @param lineno 行号
* @param colno 列号
* @param error Error对象
*/
window.onerror = function (event, source, lineno, colno, error) {
// 上报错误
// 如果不想在控制台抛出错误,只需返回 true 即可
};
//-----------------------------------
/**
* @param event 事件名
* @param function 回调函数
* @param useCapture 回调函数是否在捕获阶段执行,默认是false,在冒泡阶段执行
*/
window.addEventListener('error', (event) => {
// addEventListener 回调函数的离散参数全部聚合在 error 对象中
// 上报错误
}, true)
// Promise错误捕获------------------------
// 当 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)
}
框架错误
框架提供了 API 来捕获全局错误。在 Vue 中,提供了 errorHandler 来捕获错误(vue2,vue3)。
微信小程序提供了app.onError
的方法。
React 提供了错误边界组件,它可以捕获其子组件任何位置的 JS 错误,并且会渲染出备用 UI。如果在 class 组件中定义了static getDerivedStateFromError()
或者componentDidCatch()
这两个方法中任何一个时,该组件就变成了错误边界。
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
// Vue 3.x
app.config.errorHandler = (err, instance, info) => {
// 处理错误,例如:报告给一个服务
}
请求错误监控
前端请求有两种方案 ajax
或者 fetch
,只需重写两种方法,进行代理,即可实现请求错误监控。
代理的核心在于使用 apply
重新执行原有方法,并且在执行原有方法之前进行监听操作。
在请求错误监控中,我们关心三种错误事件:abort
,error
以及 timeout
,所以,只需在代理中对这三种事件进行统一处理即可。
ps:可以使用请求工具如 axios
,不需要重写 ajax
或者 fetch
只需在请求拦截器以及响应拦截器进行处理上报即可。
资源错误监控
资源错误一般是指页面上的静态资源路径错误,如图片src。可以通过监控error
错误事件实现错误捕获。
我们可以通过 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);
Ps:使用 addEventListener
捕获资源错误时,一定要将 useCapture
即第三个选项设为 true
,因为资源错误没有冒泡,所以只能在捕获阶段捕获。同理,由于 window.onerror
是通过在冒泡阶段捕获错误,所以无法捕获资源错误。
跨域脚本错误捕获
为了性能方面的考虑,我们一般会将脚本文件放到 CDN ,这种方法会大大加快首屏时间。但是,如果脚本报错,此时,浏览器出于于安全方面的考虑,对于不同源的脚本报错,无法捕获到详细错误信息,只会显示 Script Error
。那么,有解决该问题的方案吗?
- 方案一:所有的脚本全部放到同一源下,但是,该方案会放弃
CDN
,降低性能。(可以使用Nginx做代理) - 方案二:在
script
标签中,添加crossorigin
属性(推荐使用webpack
插件自动添加);同时,配置CDN
服务器,为跨域脚本配上CORS
。
方案二基本可以完美解决跨域脚本错误捕获的问题。但是,crossorigin
属性对于 IE 以及 Safari 支持程度不高。
错误上报方式
采用Ajax上报、使用image对象模拟图片请求方式上报。
使用Ajax请求上报,要对错误进行防抖操作,避免频繁上报重复错误。
一般来说,大厂都是采用利用image对象的方式上报错误的;使用图片发送get请求,上报信息,由于浏览器对图片有缓存,同样的请求,图片只会发送一次,避免重复上报。
var entry = {};
function report(url, data) {
if (!url || !data) {
return;
}
// @see http://jsperf.com/new-image-vs-createelement-img
var image = document.createElement('img');
var items = [];
for (var key in data) {
if (data[key]) {
items.push(key + '=' + encodeURIComponent(data[key]));
}
}
var name = 'img_' + (+new Date());
entry[name] = image;
image.onload = image.onerror = function () {
console.log(arguments);
entry[name] =
image =
image.onload =
image.onerror = null;
delete entry[name];
};
image.src = url + (url.indexOf('?') < 0 ? '?' : '&') + items.join('&');
}
错误上报需要的数据内容有:时间、message、错误类型、错误文件路径、source-map文件等。
另外,前台可建立数据可视化平台,对错误进行实时监控。
本文固定连接:https://code.zuifengyun.com/2022/09/3080.html,转载须征得作者授权。