window.addEventListener('load', function () {
// 进页面,首先检测上次是否崩溃
if(sessionStorage.getItem('good_exit') &&
sessionStorage.getItem('good_exit') !== 'true') {
/*
insert crash logging code here
*/
console.log('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
}
sessionStorage.setItem('good_exit', 'pending');
setInterval(function () {
sessionStorage.setItem('time_before_crash', new Date().toString());
}, 1000);
});
window.addEventListener('beforeunload', function () {
// 离开页面前,重置标志位
sessionStorage.setItem('good_exit', 'true');
});
前端监控体系怎么搭建?
背景
前端一直是距离用户最近的一层,随着产品的日益完善,我们会更加注重用户体验,而前端异常特别是外网客户异常,一直是前端开发的痛点。最近在家办公,对公司的监控系统,又做了一遍复习,特作此记录。
异常是不可控的,会影响最终的呈现结果,所以任何一个成熟的前端团队,都有充分的理由去做这样的事情:
1.成熟的工程化,前端监控系统必不可少;
2.远程定位问题,对于对外web页面,让客户配合找bug是一件及其不职业且低效的事情;
3.错误预警上报,及早发现并修复问题;
4.问题复现,尤其是移动端,机型,系统都是问题;
对于
JS
而言,我们面对的仅仅只是异常,异常的出现不会直接导致JS
引擎崩溃,最多只会使当前执行的任务终止。需要处理哪些异常?
对于前端来说,我们可做的异常捕获还真不少。总结一下,大概如下:
JS
语法错误、代码运行异常Http
请求异常Promise
异常Iframe
异常Script error
下面针对每种具体情况来说明如何处理这些异常。
点兵点将
一、Try-Catch 错误捕获
try-catch
只能捕获到同步的运行时错误,对语法和异步错误却无能为力。1.同步运行时错误:
输出:错误捕获: ReferenceError: ver is not defined at:3:14
2.不能捕获到语法错误,我们修改一下代码,删掉一个单引号:
以上两种包括多种语法错误,在我们开发阶段基本Eslint就会捕获到,在线上环境出现的可能性比较小,如果是,那就是前端工程化基础不好。
3.异步错误
二、window.onerror 信息全面,但不是万能的
当
JS
运行时错误发生时,window
会触发一个ErrorEvent
接口的error
事件,并执行window.onerror()
。1.首先试试同步运行时错误
可以看到,我们捕获到了异常:
2.来试试异步运行时错误:
控制台输出了:
3.接着,我们试试网络请求异常的情况:
我们发现,不论是静态资源异常,或者接口异常,错误都无法捕获到。
在实际的使用过程中,
onerror
主要是来捕获预料之外的错误,而try-catch
则是用来在可预见情况下监控特定的错误,两者结合使用更加高效。问题又来了,捕获不到静态资源加载异常怎么办?
三、window.addEventListener 静态资源加载错误
当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个
Event
接口的error
事件,并执行该元素上的onerror()
处理函数。这些error
事件不会向上冒泡到window
,但可以被window.addEventListener error
监听捕获。控制台输出:
由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断
HTTP
的状态是404
还是其他比如500
等等,所以还需要配合服务端日志才进行排查分析才可以。需要注意:
error
对象可能不同,需要注意兼容处理。addEventListener
重复监听。四、Promise Catch
在
promise
中使用catch
可以非常方便的捕获到异步error
,这个很简单。没有写
catch
的Promise
中抛出的错误无法被onerror
或try-catch
捕获到,所以我们务必要在Promise
中不要忘记写catch
处理抛出的异常。解决方案: 为了防止有漏掉的
Promise
异常,建议在全局增加一个对unhandledrejection
来全局监听Uncaught Promise Error
:测试列子: 见actionTest/index.js
可以看到如下输出:
提醒一句:与onError 采用return true来结束控制器的默认错误打印,unhandledrejection如果去掉控制台的异常显示,需要加上:
虽然可以使用增加
unhandledrejection
的监听来捕获promise的异常处理,但处理fetch
或者ajax
的异常捕获,还是不太适合,因为他只能捕获到这个错误,而无法获取错误出现的位置和错误详情;五、Http请求错误
在使用
ajax
或者fetch
请求数据时, 这里主要说Fetch, 以上说过unhandledrejection能捕获到请求的异常,但没法获取到请求的详情,哪个url 发起,传参是什么,一无所知。所有这里最好的方式就是重写fetch,具体操作:还是上面的测试列子: 见actionTest/index.js
可以看到如下输出:
六、React 框架异常捕获
在日常开发中,web开发基本都是基于React和Vue这种成熟的UI框架来做,因为工作里只用到了
React
,所以这里不涉及Vue
。 16提供了两个钩子componentDidCatch
与getDerivedStateFromError
,详情见官方文档, 使用他们可以非常简单的获取到react
下的错误信息;详细见代码src/index.js
需要注意的是:
Error boundaries
并不会捕捉下面这些错误。1.事件处理器
2.异步代码
3.服务端的渲染代码
4.在
error boundaries
区域内的错误实际使用中,我们只在根组件去可以定义一个
error boundary
组件,然后整个UI的错误都通过这里上报!像Dva这种框架,也在最外层提供了上报入口。七、Script error
一般情况,如果出现
Script error
这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:跨源资源共享机制(
CORS
):我们为script
标签添加crossOrigin
属性。特别注意,服务器端需要设置:Access-Control-Allow-Origin
此外,我们也可以试试这个-解决 Script Error 的另类思路:
简单解释一下:
EventTarget
的addEventListener
方法;listener
进行包装,返回包装过的listener
,对其执行进行try-catch
;try-catch
起来的异常进行跨域拦截,所以catch
到的时候,是有堆栈信息的;throw
出来异常的时候,执行的是同域代码,所以window.onerror
捕获的时候不会丢失堆栈信息;利用包装
addEventListener
,我们还可以达到「扩展堆栈」的效果:八、iframe 异常
对于
iframe
的异常捕获,可以通过window.onerror
来实现,一个简单的例子可能如下:现在iframe在前端中应用比较少,这里不再展开
九、崩溃和卡顿
卡顿也就是网页暂时响应比较慢,通常我们说的60fps, 就是描述这个的,卡顿的现象就是造成
JS
无法及时执行。但崩溃就不一样了,崩溃直接造成JS
不运行了,JS执行进程卡死,相比网页崩溃上报更难?崩溃和卡顿都是不可忽视的,都会导致用户体验不好,而加剧用户流失。1、卡顿的实现相对比较简单,我们可以通过requestAnimationFrame采集样本,来判断页面是否长期(几秒内)低于30fps或其他阈值。
看下面具体实现:
2、崩溃的监控相对于稍显复杂来说,在这篇文章讲的很清楚:网页崩溃的监控:
window.addEventListener('beforeunload', function () { // 离开页面前,重置标志位 sessionStorage.setItem('good_exit', 'true'); });
总结
以上基本涵盖了监控系统中90%以上的错误捕获案例,但这只是监控系统的开端,只能算是Demo级别的代码。市面上有很多成熟的监控库可参考,比如FunderBug,Raven-Js等,我们团队的监控库就是是在Raven上做了一层扩展,然后结合IndexDb和压缩库(pako),以及服务端日志收集采用Koa来实现,知识点很多,但前面这些非常重要。
参考
前端代码异常监控实战
如何优雅处理前端异常?
Error Boundaries
前端监控知识点
Capture and report JavaScript errors with window.onerror