注:
- 2020年10月22日
- 补充了技术细节,合并类似重复文章
- 补充相关话题索引。
1 基本概念
本节介绍什么是前端监控,监控什么,监控原理。
什么是前端监控
提起前端监控我们在说什么?一般是指三个方面:
类型 | 场景 | 技术实现 |
---|---|---|
前端错误 | 用户浏览器报错 | 捕获错误上报 |
用户行为 | 用户行为轨迹 | 埋点上报 |
页面性能指标 | 页面渲染情况 | 关键节点上报 |
前端有什么错误
前端项目运行时候会发生bug,大致分为几类:
- 前端逻辑错误,比如类型错误、引用错误
- 接口请求异步 async/await 错误
- 资源加载失败
捕获的方式也不尽相同。
如何捕获错误
这里我配合做了一个Git仓库辅助说明,很多技术细节代码中提及。
仓库地址:web-error-demo
js运行出现的错误
我们可以通过 window.onerror
来拦截大部分错误
资源加载的错误
因为不会冒泡,可以通过 window.addEventListener('error')
来捕获
跨域代码报错会提示 script error
,需要给 script 标签添加 crossorigin
属性
异步代码
- 统一在
try/catch
promise.catch
中捕获 - 异步错误通过
unhandledrejection
来统一收底
网络请求
考虑重写 xhr.prototype
和 fetch
,项目中可以通过 axios
拦截器来统一处理,这意味着我们需要提前做好判断,不在业务逻辑中处理错误。
Vue框架
如果是Vue项目,可以设置全局的选项: errorHandler 来处理组件渲染和运行期间未捕获的错误。
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
此处可索引我的读书笔记: 书籍《从零开始搭建前端监控平台》
错误处理
经过排查发现,Chrome和Firefox对error的包装不同,这里涉及到了大量的兼容代码。
err:
- line colum 和 columnNumber
- col line 和 lineNumber
- err.name
- stack 字符串截取
通过前端构建会压缩混淆代码,这里需要用到 source-map
插件来做映射,通过输入错误信息回溯到源码位置。
2 处理上报
我们捕获了来自用户的错误,应当上报给远程,统一入库。
先从简单的概念说起。
上报错误信息一般使用三种:
技术选型 | 场景 | 备注 |
---|---|---|
get | 简单信息量小,简单 | |
post | 长度无限制,可复杂数据 | |
head | 只上报无返回 | 充分利用http特性 |
new Image() | 简单,有效 | 无视跨域 |
sendBacon | 关闭、跳转页面,侵入低 | 新特性,特点明显 |
其中 head 是HTTP请求方法中不太常用的一种,特点是没有返回体。恰巧我们也不在乎返回结果。
其中 sendBeacon
值得一说,很有特点。MDN文档在此
有一些技术被用来保证数据的发送。其中一种是通过在卸载事件处理器中创建一个图片元素并设置它的 src 属性的方法来延迟卸载以保证数据的发送。因为绝大多数用户代理会延迟卸载以保证图片的载入,所以数据可以在卸载事件中发送。另一种技术是通过创建一个几秒钟的 no-op 循环来延迟卸载并向服务器发送数据。— MDN
用法很简单,在任意时间发起请求:navigator.sendBeacon(url, data);
特点:异步非阻塞,不竞争网络资源+ 可靠
信标请求可以有效地合并,以优化移动设备上的能量使用。
主要解决 beforenload
unload
时候的异步请求会被忽略的问题。
异步、可靠。当然了 ie不兼容。
web.dev 是这样写的:
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToAnalytics({name, value, id}) {
const body = JSON.stringify({name, value, id});
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics', {body, method: 'POST', keepalive: true});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
beforeunload和unload 事件都不可靠(尤其是在移动设备上),并且不建议使用 它们(因为它们可以阻止页面有资格使用Back-Forward Cache)。
对于跟踪页面整个生命周期的指标,最好在visibilitychange事件的可见性状态变为时发送事件期间指标的当前值hidden。这是因为,一旦页面的可见性状态更改为,就hidden无法保证该页面上的任何脚本都能再次运行。在移动操作系统上尤其如此,在移动操作系统中,浏览器应用程序本身可以关闭,而无需触发任何页面回调。