错误信息收集方案:
前端错误分为:代码执行错误、资源加载错误
一、try catch
1try{
2 // 代码块
3} catch(e){
4 // 错误处理、这块可以进行上报
5}
这种方式需要开发者手动对预估存在错误风险进行包裹,这种可以手动完成也可以通过自动化工具和类库完成。自动化工具也是基于AST实现的,比如UglityJS等。
try catch的特点:对于处理运行时非异步错误没问题,但无法处理语法错误和异步错误。
1// 能捕获
2try{
3 a // 未定义变量
4} catch(e){
5 console.log(e)
6}
7// 不能捕获--语法错误
8try{
9 var a = \ 'a'
10}catch(e){
11 console.log(e)
12}
13// 不能捕获--异步
14try{
15 setTimeout(()=>{
16 a
17 })
18} catch(e){
19 console.log(e)
20}
21// 在setTimeout里面再加一层try catch才会生效
总结:try catch有局限性,且对代码侵入性强
二、window.onerror—事件冒泡的方式处理
window.onerror的特点:能对语法异常和运行时异常(异步也能捕获)进行处理,但是对语法错误和网络错误(因为网络请求异常不会发生事件冒泡)无能为力,代码侵入性小,不必通过AST向代码中自动插入脚本。
1window.onerror = function(message, source, lineno, colno, error) { ... }
- message:错误消息(字符串)。
- source:引发错误的脚本的URL(字符串)
- lineno:发生错误的行号(数值)
- colno:发生错误的行的列号(数值)
- error:错误对象(对象),比如error.stack会获取错误的堆栈信息
跨域脚本的错误处理:
对于不同域的js文件,window.onerror不能捕获到有效信息。出于安全原因,不同浏览器返回的错误信息参数可能不一致。跨域之后window.onerror在很多浏览器中是无法捕获异常信息的,统一会返回脚本错误(script error)。所以需要对脚本进行设置
1crossorigin="anonymous"
使用source map进行错误还原
三、Promise的错误信息处理
提倡Promise最后写上catch函数的习惯,可以通过ESLint插件eslint-plugin-promise检查。
promise的错误通过捕获事件unhandlerejection
1window.addEventListener('unhandledrejection',e=>{
2 console.log(e)
3})
四、网络异常错误处理
可以进行如下操作:
1<script src"**.js" onerror="errorHandle(this)"></script>
或者使用:window.addEventListener(‘error’) 事件捕获
1window.addEventListener('error',e=>{
2 console.log(e)
3},true)
那这两个error:window.onerror和window.addEventListener(‘error’)怎么区分网络加载错误和其他一般错误,可以通过以下方式
1window.addEventListener('error',e=>{
2 if(!e.message){
3 // 网络资源加载错误
4 }
5},true)
window.onerror和window.addEventListener(‘error’)区别:
-
window.onerror:冒泡,需要进行函数赋值,重复声明会被替换
-
window.addEventListener(‘error’):捕获,可以绑定多个回调函数,按照绑定顺序执行
五、页面崩溃收集和处理
通过监听window对下的load和beforeunload进行处理并结合sessionStorage
1window.addEventListener('load',()=>{
2 sessionStorage.setTitem('page_exit','pending')
3})
4window.addEventListener('beforeunload',()=>{
5 sessionStorage.setTitem('page_exit','true')
6})
7
8sessionStorage.getTitem('page_exit')!='true' // 页面崩溃
六、vue框架的错误处理
Vue.config.errorHandler捕获的错误会以console.error的方式输出
1Vue.config.errorHandler = function (err, vm, info) { reportError({ code: 'xx', msg: encodeURIComponent(err), action: 'vue-render-error', line: info, attach2: err && err.stack || '' });};
因此可以劫持console.error来捕获框架中的错误
1const nativeConsoleError = window.console.error
2
3window.console.error = (..args)=>nativeConsoleError.apply(this,args)
性能数据
监控指标:首次绘制时间(FP)、首次有内容绘制(FCP)时间、 首次有意义绘制时间(FMP)、首屏时间、用户可交互(TTI)时间

关键数据耗时:
1var navigasitonInfo = null;if (typeof performance != 'undefined' && typeof performance.getEntriesByType != 'undefined') { navigasitonInfo = performance.getEntriesByType('navigation')[0];};
2let timing = window.performance.timing;
3//页面完全加载耗时let complete_load_duration = timing.loadEventEnd - timing.navigationStart
4// 页面白屏时间
5let white_screen_duration = time.domLoading - timing.navigationStart
6// 首屏时间
7let first_screen_duration = calcFirstScreen()
8// 可交互
9
10let interactive_duration = timing.domContentLoadedEventEnd - timing.navigationStart
11// 准备耗时
12let stalled_duration= timing.domainLookupStart - timing.navigationStart
13// 重定向耗时
14let redirect_duration = timing.redirectEnd - timing.redirectStart
15// DNS解析耗时
16let dns_duration = timing.domainLookupEnd - timing.domainLookupStart
17// ip连接耗时
18let ip_connect_duration = timing.connectEnd - timing.connectStart
19// 首包耗时
20let first_data_duration = timing.responseStart - timing.requestStart
21// 完整包加载耗时
22let final_data_duration = timing.responseEnd - (timing.requestStart?timing.requestStart:timing.fetchStart)),
23// dom处理
24let dom_operate_duration = timing.domComplete - timing.domLoading
25// 资源加载耗时
26let res_load_duration = info.duration
27// 当前页面文件大小
28let res_size = navigasitonInfo ? navigasitonInfo.transferSize : 0
29function calcFirstScreen() { var timing = window.performance.timing; var firstScreen = timing.loadEventStart - timing.navigationStart; if (typeof window.performance != 'undefined' && typeof window.performance.getEntriesByName != 'undefined'){ try{ var iHeight = window.innerHeight; var iWidth = window.innerWidth; var imgList = document.querySelectorAll('img'); var loadEventDuration = timing.loadEventEnd - timing.navigationStart; for (var i=0,j=imgList.length;i<j;i++){ var target = imgList[i]; if (typeof target.getBoundingClientRect != 'undefined' && target.src) { var rectInfo = target.getBoundingClientRect(); if (rectInfo){ var pageYOffset = window.pageYOffset; var topH = rectInfo.top + pageYOffset; if (topH < iHeight && rectInfo.left >=0 &&rectInfo.left<iWidth){ var perList = window.performance.getEntriesByName(target.src); if (perList.length){ var targetPer = perList[0]; if (targetPer.fetchStart < loadEventDuration){ firstScreen = targetPer.responseEnd; }; }; }; }; }; }; }catch (e) { } }; return firstScreen; }
错误上报
最好采用单独域名,防止对业务服务器造成压力以及同一个域名的请求量有并发数的限制
上报方式:
1、图片的形式
图片不涉及跨域的问题,使用构造空的Image对象的方式
1let reportUrl = 'xxurl.json';let reportImg= new Image();let dataStr = paramStr(xxx);reportImg.src = newReportUrl + '?' + dataStr;
2、navigator.sendBeacon
使用 sendBeacon()
方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。
1window.addEventListener('unload', logData, false);
2
3function logData() {
4 navigator.sendBeacon("/log", analyticsData);
5}
上报时机:
- 页面加载和重新刷新
- 页面切换路由
- 页面关闭
- 页面所在的tab标签重新可见