https://juejin.cn/post/7046756775252459528
https://juejin.cn/post/6936562262480158728#heading-2
https://cloud.tencent.com/developer/article/1983779
https://zhuanlan.zhihu.com/p/495649475
为什么要创建前端监控系统
主要是为了解决两种问题:
- 如何及时发现问题
- 如何快速定位并解决问题
前端监控体系主要需要做的事情
- 页面整体访问情况,pv、uv、用户行为上报
- 页面的性能情况,包括:加载耗时、接口耗时统计
- 灰度发布和有效的监控能力,方便及时发现问题
- 用户反馈问题,需要足够的日志定位
总结起来就是两点:数据收集、数据上报
数据收集
一般是三个方面
- 稳定性(页面稳定性/异常):js错误(js执行错误、promise异常)、资源错误(js、css加载异常)、接口错误(ajax、fetch请求错误)、白屏
- 流畅性(页面访问速度)
- 用户行为回放(外部服务调用情况):pv、uv、用户在某个页面的停留时间
1、稳定性(页面稳定性/异常)
1)脚本错误
有两类,分别为【语法错误】、【运行时错误】,下面是主要监控方式:
- try catch:可以用在预知情况下监控特定错误,但发生语法错误或者异步错误时,无法捕捉,常与window.onerror结合使用
- window.onerror:捕获JS运行时错误
- window.addEventListener('unhandledrejection'):捕获promise未处理reject的错误,即promise被reject了,但是没有reject处理器,则会走到这一步(https://developer.mozilla.org/zh-CN/docs/Web/API/Window/unhandledrejection_event)
- window.addEventListener('error'):资源加载错误(
js、css加载异常
),但是他也能上报js运行时错误, 所以判断只有是script、link、image时才去拿数据
为避免重复上报js运行时错误,此时只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement时才进行数据采集。
// try catch 语法错误
try {
function empty() // <- throw error 语法错误
} catch(e){
console.log('语法错误信息 ↙');
console.log(e);
}
// try catch 异步错误
try {
setTimeout(function() {
test // <- throw error 异步错误
},0)
} catch(e){
console.log('异步错误信息 ↙');
console.log(e);
}
2)跨域资源的脚本报错Script error
常见的形式就是,我本来的网站域名是a.com,然后,里面使用script引入了一个cdn连接,cdn连接域名是b.com,这个时候在调用cdn里的方法时就会报Script error,但是获取不到完整的信息,只能获取到类似于:"Script error.", "", 0, 0, undefined的信息
解决方式:
- 为页面上script标签添加crossorigin属性
// 这一步告诉浏览器,目标脚本通过匿名方式获取。这意味着请求脚本时没有潜在的用户身份信息(如cookies、HTTP 证书等)发送到服务端
<script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>
- 响应头中增加 Access-Control-Allow-Origin 来支持跨域资源共享,使用*或者上面本来的网站即http://a.com
- 当 Access-Control-Allow-Origin的值为http://a.com时,响应头中需带上Vary:Origin。避免引缓存导致的权限问题。(ps:为*的时候无所谓,因为所有的域名都让他去请求了)
通过以上方式可以使用window.onerro获取到具体的报错信息
3)接口异常
通过重写XMLHttpRequest和fetch的原生方法来实现
- 重写XMLHttpRequest的open、send方法
- 监听load、error、abort事件
window.addEventListener("error", handler("error"), false);
4)资源加载异常
页面内的图片、css、JS等Assets资源加载失败,会在error事件里可以拿到资源加载失败回调:
window.addEventListener( 'error ', function(e){
//排除JSError
if( ! (e instanceof ErrorEvent)){
//资源路径
e.target.src || e.target.href
//资源类型
e.target.tagName
}
}, true) // 为true则是捕获阶段处理函数,否则为冒泡阶段处理函数
5)白屏问题
页面加载过程中因为任意问题导致渲染没有完成,呈现空白页面的异常
一般基于不同方案会有不同的处理方式
1、基于mutationObserver:页面加载完成后3s,页面没有节点变化(节点不变不代表不白屏)
2、基于native容器:页面加载完成后3s,页面还是全屏白色像素(依赖容器)
3、基于pointTiming:页面加载完成后3s,页面没有fisrt-point,下方为实现(兼容性不好)
- document.elementsFromPoint方法可以获取到当前视口内指定坐标处,由里到外排列的所有元素
- 根据 elementsFromPoint api,获取屏幕水平中线和竖直中线所在的元素(9点监测)
for (let i = 1; i <= 9; i++) {
xElements = document.elementsFromPoint(
(window.innerWidth * i) / 10,
window.innerHeight / 2
);
yElements = document.elementsFromPoint(
window.innerWidth / 2,
(window.innerHeight * i) / 10
);
isWrapper(xElements[0]);
isWrapper(yElements[0]);
}
6)crash
页面因为内存溢出、死循环等原因导致崩溃的异常
1、基于LocalStroage里的页面离开状态
- 在页面加载时,标记开始加载
- 在pagehide或者beforunload时标记离开,二次进入页面时判断是否正常离开
- 埋点发送滞后,起不到监控告警作用
2、基于native
- 监控webview进程状态,发送crash日志
- 依赖容器
3、基于service worker
- html请求进入service worker时,标记页面开始加载
- 每隔一段时间像sw发送一次心跳,当一段时间没有收到心跳则表示crash了
- 兼容性差,一个页面只能有一个sw,如果页面也需要使用sw,则需进行嵌入,切换页面又可能pause
2、流畅性(页面访问速度)
1)卡顿
响应用户交互的响应时间如果大于100ms,用户就会感觉卡顿
卡顿:页面整个生命周期中,主线程持续执行某一个任务的耗时大于50ms
浏览器的事件队列机制决定,要实现小于100毫秒的响应,应用必须在每50毫秒内将控制返回给主线程
- new PerformanceObserver
- entry.duration(持续时间) > 100 判断大于100ms,即可认定为长任务(entry是参数,详见第三个链接)
- 使用 requestIdleCallback上报数据
2)PV、UV、用户停留时间
PV(page view) 是页面浏览量,UV(Unique visitor)用户访问量。PV 只要访问一次页面就算一次,UV 同一天内多次访问只算一次。
对于前端来讲,只要每次进入页面上报一次 PV 就行,UV 的统计放在服务端来做
window.addEventListener(
"beforeunload",
() => {},
false// 冒泡的时候做回调
}
3)加载时间(onload时监听)
3、用户行为回放(外部服务调用情况)【可以先不说】
数据上报
主要有三种方式处理信息上报
丢点:在浏览器点击跳转时,跳转前的点击上报请求都会进行一个三次握手,如果此时,网络较慢、服务器运行缓慢或者上报请求还在处理阶段,这时,如果页面被卸载了,浏览器都会自动对当前的请求进行终止。这样,这个http的请求就没有建立,导致上报没有真正发出。
延迟卸载:在卸载事件处理器中尝试通过一个同步的 XMLHttpRequest 向服务器发送数据。这导致了页面卸载被延迟。
img请求
- 兼容性好
- 部分浏览器可能造成丢点、get请求长度限制,延迟页面卸载
- 就是动态js创建img标签,把上报的url拼接,然后放在src上
Fetch/XHR(XMLHttpRequest)
- 兼容性好
- fetch丢点,XHR不丢点,但是页面延迟卸载
Navigator.sendBeacon(推荐使用)
sendBeacon()会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能
- 不丢点,不会页面延迟卸载
- 兼容性不好