错误信息收集方案:
前端错误分为:代码执行错误、资源加载错误
一、try catch
try{
// 代码块
} catch(e){
// 错误处理、这块可以进行上报
}
复制代码
这种方式需要开发者手动对预估存在错误风险进行包裹,这种可以手动完成也可以通过自动化工具和类库完成。自动化工具也是基于AST实现的,比如UglityJS等。
try catch的特点:对于处理运行时非异步错误没问题,但无法处理语法错误和异步错误。
// 能捕获
try{
a // 未定义变量
} catch(e){
console.log(e)
}
// 不能捕获--语法错误
try{
var a = \ 'a'
}catch(e){
console.log(e)
}
// 不能捕获--异步
try{
setTimeout(()=>{
a
})
} catch(e){
console.log(e)
}
// 在setTimeout里面再加一层try catch才会生效
复制代码
总结:try catch有局限性,且对代码侵入性强
二、window.onerror–事件冒泡的方式处理
window.onerror的特点:能对语法异常和运行时异常(异步也能捕获)进行处理,但是对语法错误和网络错误(因为网络请求异常不会发生事件冒泡)无能为力,代码侵入性小,不必通过AST向代码中自动插入脚本。
window.onerror = function(message, source, lineno, colno, error) { ... }
复制代码
- message:错误消息(字符串)。
- source:引发错误的脚本的URL(字符串)
- lineno:发生错误的行号(数值)
- colno:发生错误的行的列号(数值)
- error:错误对象(对象),比如error.stack会获取错误的堆栈信息
跨域脚本的错误处理:
对于不同域的js文件,window.onerror不能捕获到有效信息。出于安全原因,不同浏览器返回的错误信息参数可能不一致。跨域之后window.onerror在很多浏览器中是无法捕获异常信息的,统一会返回脚本错误(script error)。所以需要对脚本进行设置
crossorigin="anonymous"
复制代码
使用source map进行错误还原
三、Promise的错误信息处理
提倡Promise最后写上catch函数的习惯,可以通过ESLint插件eslint-plugin-promise检查。
promise的错误通过捕获事件unhandlerejection
window.addEventListener('unhandledrejection',e=>{
console.log(e)
})
复制代码
四、网络异常错误处理
可以进行如下操作:
<script src"**.js" onerror="errorHandle(this)"></script>
复制代码
或者使用:window.addEventListener(‘error’) 事件捕获
window.addEventListener('error',e=>{
console.log(e)
},true)
复制代码
那这两个error:window.onerror和window.addEventListener(‘error’)怎么区分网络加载错误和其他一般错误,可以通过以下方式
window.addEventListener('error',e=>{
if(!e.message){
// 网络资源加载错误
}
},true)
复制代码
window.onerror和window.addEventListener(‘error’)区别:
-
window.onerror:冒泡,需要进行函数赋值,重复声明会被替换
-
window.addEventListener(‘error’):捕获,可以绑定多个回调函数,按照绑定顺序执行
五、页面崩溃收集和处理
通过监听window对下的load和beforeunload进行处理并结合sessionStorage
window.addEventListener('load',()=>{
sessionStorage.setTitem('page_exit','pending')
})
window.addEventListener('beforeunload',()=>{
sessionStorage.setTitem('page_exit','true')
})
sessionStorage.getTitem('page_exit')!='true' // 页面崩溃
复制代码
六、vue框架的错误处理
Vue.config.errorHandler捕获的错误会以console.error的方式输出
Vue.config.errorHandler = function (err, vm, info) { reportError({ code: 'xx', msg: encodeURIComponent(err), action: 'vue-render-error', line: info, attach2: err && err.stack || '' });};
复制代码
因此可以劫持console.error来捕获框架中的错误
const nativeConsoleError = window.console.error
window.console.error = (..args)=>nativeConsoleError.apply(this,args)
复制代码
性能数据
监控指标:首次绘制时间(FP)、首次有内容绘制(FCP)时间、 首次有意义绘制时间(FMP)、首屏时间、用户可交互(TTI)时间
关键数据耗时:
var navigasitonInfo = null;if (typeof performance != 'undefined' && typeof performance.getEntriesByType != 'undefined') { navigasitonInfo = performance.getEntriesByType('navigation')[0];};
let timing = window.performance.timing;
//页面完全加载耗时let complete_load_duration = timing.loadEventEnd - timing.navigationStart
// 页面白屏时间
let white_screen_duration = time.domLoading - timing.navigationStart
// 首屏时间
let first_screen_duration = calcFirstScreen()
// 可交互
let interactive_duration = timing.domContentLoadedEventEnd - timing.navigationStart
// 准备耗时
let stalled_duration= timing.domainLookupStart - timing.navigationStart
// 重定向耗时
let redirect_duration = timing.redirectEnd - timing.redirectStart
// DNS解析耗时
let dns_duration = timing.domainLookupEnd - timing.domainLookupStart
// ip连接耗时
let ip_connect_duration = timing.connectEnd - timing.connectStart
// 首包耗时
let first_data_duration = timing.responseStart - timing.requestStart
// 完整包加载耗时
let final_data_duration = timing.responseEnd - (timing.requestStart?timing.requestStart:timing.fetchStart)),
// dom处理
let dom_operate_duration = timing.domComplete - timing.domLoading
// 资源加载耗时
let res_load_duration = info.duration
// 当前页面文件大小
let res_size = navigasitonInfo ? navigasitonInfo.transferSize : 0
function 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对象的方式
let reportUrl = 'xxurl.json';let reportImg= new Image();let dataStr = paramStr(xxx);reportImg.src = newReportUrl + '?' + dataStr;
复制代码
2、navigator.sendBeacon
使用 sendBeacon()
方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}
复制代码
上报时机:
- 页面加载和重新刷新
- 页面切换路由
- 页面关闭
- 页面所在的tab标签重新可见