注:
- 2020年10月22日
- 补充了技术细节,合并类似重复文章
- 补充相关话题索引。
1 基本概念
本节介绍什么是前端监控,监控什么,监控原理。
什么是前端监控
提起前端监控我们在说什么?一般是指三个方面:
| 类型 | 场景 | 技术实现 |
|---|---|---|
| 前端错误 | 用户浏览器报错 | 捕获错误上报 |
| 用户行为 | 用户行为轨迹 | 埋点上报 |
| 页面性能指标 | 页面渲染情况 | 关键节点上报 |
前端有什么错误
前端项目运行时候会发生bug,大致分为几类:
- 前端逻辑错误,比如类型错误、引用错误
- 接口请求异步 async/await 错误
- 资源加载失败
捕获的方式也不尽相同。
如何捕获错误
这里我配合做了一个Git仓库辅助说明,很多技术细节代码中提及。
仓库地址:web-error-demo
js运行出现的错误
我们可以通过 window.onerror 来拦截大部分错误
资源加载的错误
因为不会冒泡,可以通过 window.addEventListener('error') 来捕获
跨域代码报错会提示 script error ,需要给 script 标签添加 crossorigin 属性
异步代码
- 统一在
try/catchpromise.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无法保证该页面上的任何脚本都能再次运行。在移动操作系统上尤其如此,在移动操作系统中,浏览器应用程序本身可以关闭,而无需触发任何页面回调。
