【前言】前端异常:用户在使用Web应用时未能达到自己或系统规划的预期,给客户带来较差的体验,影响用户的满意度。一款研发产品上线后,如何把控产品质量,做好监控收尾,决定着产品的寿命和未来的走向,目前后端有比较成熟的监控方案,如应用日志收集方案graylog,elk等。前端却很少有监控类型的系统,接下来梳理一下自己总结的前端监控方案,方便我们快速定位到问题(甩锅)。
前端监控按类型照大致分为行为监控、异常监控、性能监控
行为监控:统计用户的pv/uv,使用率,跳出率,访问量,页面操作行为等,前端行为一般用埋点来实现,目前较为成熟的产品有百度统计,google analytics
2. 异常监控:
@异常分类:
1.前端错误的分类:及时运行错误(代码错误)
逻辑判断引发的报错
数据引用引发的报错
var ts = typescript;
语法错误引发的报错
console.log ('hi';
]范围越界错误
new Array(-1)
// Uncaught URIError: URI malformed
decodeURI('%dfd')
2.资源加载错误
文件404
文件403
文件跨域访问不到
@错误捕捉:
及时运行错误(代码错误)
全局方法:window.onerror
a、全局捕获
通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有:
window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等
框架级别的全局监听,例如aixos中使用interceptor进行拦截,vue、react都有自己的错误采集接口
通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常
对实例方法重写(Patch),在原有功能基础上包裹一层,例如对console.error进行重写,在使用方法不变的情况下也可以异常捕获
局部方法:try catch
b、单点捕获
在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:
try…catch
专门写一个函数来收集异常信息,在异常发生时,调用该函数
专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行结果和原函数一模一样,只是在发生异常时可以捕获异常
两者配合使用
资源加载错误
a)object.onerror
b)performance.getEntries() 拿到所有已经加载的资源,在onload事件中遍历出所有页面资源集合(css/image/js/font等), 利用排除法,拿到所有的js,htrml.css,img等文件
c) Error事件捕获
针对特殊情况,跨域js处理方案
方案一:(设置domain)
将js内联到HTML中
将js文件与HTML放在同域下
方案二:(设置允许跨域,需要后端配合)
为页面上script标签添加crossorigin属性
被引入脚本所在服务端响应头中,增加Access-Control-Allow-Origin 来支持跨域资源共享
捕获资源不存在问题
错误上报
上报错误的基本原理:
一般而言,监控系统大致可以分为四个阶段:日志采集、日志存储、统计与分析、报告和警告。
收集阶段:收集异常日志,先在本地做一定的处理,采取一定的方案上报到服务器。
存储阶段:后端接收前端上报的异常日志,经过一定处理,按照一定的存储方案存储。
分析阶段:分为机器自动分析和人工分析。机器自动分析,通过预设的条件和算法,对存储的日志信息进行统计和筛选,发现问题,触发报警。人工分析,通过提供一个可视化的数据面板,让系统用户可以看到具体的日志数据,根据信息,发现异常问题根源。
报警阶段:分为告警和预警。告警按照一定的级别自动报警,通过设定的渠道,按照一定的触发规则进行。预警则在异常发生前,提前预判,给出警告。
利用image对象特殊属性实现快速上报
利用image对象上报错误
采用Ajax通信的上报方式
采集内容
a. 用户信息
出现异常时该用户的信息,例如该用户在当前时刻的状态、权限等,以及需要区分用户可多终端登录时,异常对应的是哪一个终端。
b. 行为信息
用户进行什么操作时产生了异常:所在的界面路径;执行了什么操作;操作时使用了哪些数据;当时的API吐了什么数据给客户端;如果是提交操作,提交了什么数据;上一个路径;上一个行为日志记录ID等。
c. 异常信息
产生异常的代码信息:用户操作的DOM元素节点;异常级别;异常类型;异常描述;代码stack信息等。
d. 环境信息
网络环境;设备型号和标识码;操作系统版本;客户端版本;API接口版本等。
异常级别
一般而言,我们会将收集信息的级别分为info,warn,error等,并在此基础上进行扩展。
info :异常发生对用户无影响,优先级不高
warn:警告异常,优先级居中(如大规模的ip地址访问)
eror :影响用户使用,优先级最高
存储日志
我们前面提到,我们并不单单采集异常本身日志,而且还会采集与异常相关的用户行为日志。单纯一条异常日志并不能帮助我们快速定位问题根源,找到解决方案。但如果要收集用户的行为日志,又要采取一定的技巧,而不能用户每一个操作后,就立即将该行为日志传到服务器,对于具有大量用户同时在线的应用,如果用户一操作就立即上传日志,无异于对日志服务器进行DDOS攻击。因此,我们先将这些日志存储在用户客户端本地,达到一定条件之后,再同时打包上传一组日志。
那么,如何进行前端日志存储呢?我们不可能直接将这些日志用一个变量保存起来,这样会挤爆内存,而且一旦用户进行刷新操作,这些日志就丢失了,因此,我们自然而然想到前端数据持久化方案。
综合之后,IndexedDB是最好的选择,它具有容量大、异步的优势,异步的特性保证它不会对界面的渲染产生阻塞。而且IndexedDB是分库的,每个库又分store,还能按照索引进行查询,具有完整的数据库管理思维,
接下来,我们究竟应该怎么合理使用IndexedDB,保证我们前端存储的合理性呢?
上图展示了前端存储日志的流程和数据库布局。当一个事件、变动、异常被捕获之后,形成一条初始日志,被立即放入暂存区(indexedDB的一个store),之后主程序就结束了收集过程,后续的事只在webworker中发生。在一个webworker中,一个循环任务不断从暂存区中取出日志,对日志进行分类,将分类结果存储到索引区中,并对日志记录的信息进行丰富,将最终将会上报到服务端的日志记录转存到归档区。而当一条日志在归档区中存在的时间超过一定天数之后,它就已经没有价值了,但是为了防止特殊情况,它被转存到回收区,再经历一段时间后,就会被从回收区中清除。
即时上报(error类型的)
收集到日志后,立即触发上报函数。仅用于A类异常。而且由于受到网络不确定因素影响,A类日志上报需要有一个确认机制,只有确认服务端已经成功接收到该上报信息之后,才算完成。否则需要有一个循环机制,确保上报成功。
批量上报(全部类型)
将收集到的日志存储在本地,当收集到一定数量之后再打包一次性上报,或者按照一定的频率(时间间隔)打包上传。这相当于把多次合并为一次上报,以降低对服务器的压力。
区块上报(全部类型)
将一次异常的场景打包为一个区块后进行上报。它和批量上报不同,批量上报保证了日志的完整性,全面性,但会有无用信息。而区块上报则是针对异常本身的,确保单个异常相关的日志被全部上报。
用户主动提交(全部类型)
增加用户参与度,排除未发现的异常发现
压缩上报数据
3. 性能监控
主要是利用perfomance对象判断页面时长,或者采用ajax拦截,获取请求时长,本文不再赘述
本文主要是对前端异常监控的整体框架进行了研究,没有涉及到具体的技术实现,涉及到的是一些前端知识点,可以依据此做一个基础版本,然后再更新迭代
分享小结:对于异步的错误信息,try catch无法捕获到,需要进行封装捕获方法,本文不再赘述