从 0 到 1 的前端异常监控项目实战

        前端监控包括行为监控、异常监控、性能监控等,本文主要讨论异常监控。对于前端而言,和后端处于同一个监控系统中,前端有自己的监控方案,后端也有自己等监控方案,但两者并不分离,因为一个用户在操作应用过程中如果出现异常,有可能是前端引起,也有可能是后端引起,需要有一个机制,将前后端串联起来,使监控本身统一于监控系统。因此,即使只讨论前端异常监控,其实也不能严格区分前后端界限,而要根据实际系统的设计,在最终的报表中体现出监控对开发和业务的帮助。

        一般而言,一个监控系统,大致可以分为四个阶段:日志采集、日志存储、统计与分析、报告和警告。

640?wx_fmt=png

        采集阶段:收集异常日志,先在本地做一定的处理,采取一定的方案上报到服务器。

        存储阶段:后端接收前端上报的异常日志,经过一定处理,按照一定的存储方案存储。

        分析阶段:分为机器自动分析和人工分析。机器自动分析,通过预设的条件和算法,对存储的日志信息进行统计和筛选,发现问题,触发报警。人工分析,通过提供一个可视化的数据面板,让系统用户可以看到具体的日志数据,根据信息,发现异常问题根源。

        报警阶段:分为告警和预警。告警按照一定的级别自动报警,通过设定的渠道,按照一定的触发规则进行。预警则在异常发生前,提前预判,给出警告。

        前端异常是指在用户使用Web应用时无法快速得到符合预期结果的情况,不同的异常带来的后果程度不同,轻则引起用户使用不悦,重则导致产品无法使用,使用户丧失对产品的认可。

1、前端异常

        前端异常是指在用户使用Web应用时无法快速得到符合预期结果的情况,不同的异常带来的后果程度不同,轻则引起用户使用不悦,重则导致产品无法使用,使用户丧失对产品的认可。

1.1、前端异常分类

根据异常代码的后果的程度,对前端异常的表现分为如下几类

640?wx_fmt=png

a. 出错

        界面呈现的内容与用户预期的内容不符,例如点击进入非目标界面,数据不准确,出现的错误提示不可理解,界面错位,提交后跳转到错误界面等情况。这类异常出现时,虽然产品本身功能还能正常使用,但用户无法达成自己目标。

b. 呆滞

        界面出现操作后没有反应的现象,例如点击按钮无法提交,提示成功后无法继续操作。这类异常出现时,产品已经存在界面级局部不可用现象。

c. 损坏

        界面出现无法实现操作目的的现象,例如点击无法进入目标界面,点击无法查看详情内容等。这类异常出现时,应用部分功能无法被正常使用。

d. 假死

        界面出现卡顿,无法对任何功能进行使用的现象。例如用户无法登陆导致无法使用应用内功能,由于某个遮罩层阻挡且不可关闭导致无法进行任何后续操作。这类异常出现时,用户很可能杀死应用。

e. 崩溃

        应用出现经常性自动退出或无法操作的现象。例如间歇性crash,网页无法正常加载或加载后无法进行任何操作。这类异常持续出现,将直接导致用户流失,影响产品生命力。

1.2、异常错误原因分类

前端产生异常的原因主要分5类:

原因 案例 频率
逻辑错误 1)    业务逻辑判断条件错误
2)    事件绑定顺序错误
3)    调用栈时序错误
4)    错误的操作js对象
经常
数据类型错误 1)    将null视作对象读取property
2)    将undefined视作数组进行遍历
3)    将字符串形式的数字直接用于加运算
4)    函数参数未传
经常
语法句法错误
较少
网络错误 1)    慢
2)    服务端未返回数据但仍200,前端按正常进行数据遍历
3)    提交数据时网络中断
4)    服务端500错误时前端未做任何错误处理
偶尔
系统错误 1)    内存不够用
2)    磁盘塞满
3)    壳不支持API
4)    不兼容
较少

2、异常采集

2.1、采集内容

        当异常出现的时候,我们需要知道异常的具体信息,根据异常的具体信息来决定采用什么样的解决方案。在采集异常信息时,可以遵循4W原则:

WHO did WHAT and get WHICH exception in WHICH environment?

a. 用户信息

        出现异常时该用户的信息,例如该用户在当前时刻的状态、权限等,以及需要区分用户可多终端登录时,异常对应的是哪一个终端。

b. 行为信息

        用户进行什么操作时产生了异常:所在的界面路径;执行了什么操作;操作时使用了哪些数据;当时的API吐了什么数据给客户端;如果是提交操作,提交了什么数据;上一个路径;上一个行为日志记录ID等。

c. 异常信息

        产生异常的代码信息:用户操作的DOM元素节点;异常级别;异常类型;异常描述;代码stack信息等。

d. 环境信息

        网络环境;设备型号和标识码;操作系统版本;客户端版本;API接口版本等。

2.2、异常捕获

        前端捕获异常分为全局捕获和单点捕获。全局捕获代码集中,易于管理;单点捕获作为补充,对某些特殊情况进行捕获,但分散,不利于管理。

a、全局捕获

通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有:

        window.addEventListener("error")、window.addEventListener("unhandledrejection")、document.addEventListener("click") 等。

        框架级别的全局监听,例如aixos中使用interceptor进行拦截,vue、react都有自己的错误采集接口通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常,对实例方法重写(Patch),在原有功能基础上包裹一层,例如对console.error进行重写,在使用方法不变的情况下也可以异常捕获。

b、单点捕获

在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:

  • try…catch

  • 专门写一个函数来收集异常信息,在异常发生时,调用该函数

  • 专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行结果和原函数一模一样,只是在发生异常时可以捕获异常

2.3、跨域脚本异常

        由于浏览器安全策略限制,跨域脚本报错时,无法直接获取错误的详细信息,只能得到一个Script Error。例如,我们会引入第三方依赖,或者将自己的脚本放在CDN时。

解决Script Error的方法:

方案一:

  • 将js内联到HTML中

  • 将js文件与HTML放在同域下

方案二:

  1. 为页面上script标签添加crossorigin属性

  2. 被引入脚本所在服务端响应头中,增加 Access-Control-Allow-Origin 来支持跨域资源共享

3、上报方案

本文采用 error-report 进行讲解。

3.1、error-report 简介

        从简单性、可测试性和松耦合性角度而言,绝大部分前端开发者都可以从error-report中受益。解决前端异常信息统一处理的痛点,error-report给复杂的前端异常上报带来了春天。记录前端异常信息,支持断网暂存异常,在线后自动上传暂存异常信息。涵盖 Vue 异常、Axios 异常、原生Ajax 异常、JS 抛出的异常、Promise 异常、Async 异常、加载第三方CDN资源异常等,几乎涵盖了前端所有能涉及到的异常。

  • 目的:解决前端异常信息统一处理的痛点

  • 功能:记录前端异常信息,支持断网暂存异常,在线后自动上传暂存异常信息

  • 范围:任何前端应用

  • 使用:两行代码搞定,使用的复杂度几乎降低到了零

3.2、特点

  • 可拔插

  • 代码侵入量小

  • 使用灵活方便

3.3、使用

import ErrorReport from "./plugins/errorReport";	

	
Vue.use(ErrorReport, {	
    reportUrl: "http://localhost:10300/errorReport",	
    env: "dev",	
    appId: "error-report-5c6pz3e4il59k2f3b6",	
    appName: "error-report"	
});


3.4、Demo

https://github.com/sky9102/error-report/blob/master/src/views/errorTest.vue


3.4、配置参数 options


属性 说明 类型 默认值 是否可以为空
reportUrl 异常上报地址 String http://localhost:10300/errorReport N
delayTime 延时上报Error时间 Number 3000 (单位:毫秒) Y
appId 项目ID String
Y
appName 项目名称 String
Y
browser 浏览器名称 String 内部方法可以获取 N
device 设备名称 String 内部方法可以获取 N
userId userId String
Y
token token String
Y
timeSpan 发送数据时的时间戳 Number 每次取当前的时间戳 Y
infoType 信息类别,默认为error String type Y
msg 错误的具体信息 String 错误的具体信息 Y
userAgent userAgent String userAgent Y
pageUrl 上报页面地址 String window.location.href Y
stack 错误堆栈信息 String 错误堆栈信息 Y
localStorageKey 建议使用固定的key,下次用户打开浏览器可以直接恢复异常数据并上传 String localStorageKey N
env 环境:dev、test、uat、pro String 开发环境 Y
data 更多错误信息 Object 更多错误信息 Y

3.5、注意事项

考虑到有些项目使用原生Ajax,Ajax 异常做了原生的拦截,Axios 异常也做了拦截;使用了Axios的童鞋,会出现异常上报两次(原因:Axios 异常拦截器一次,原生Ajax异常 拦截一次),不想上报两次可以选择注释以下代码(Axios 异常监控Ajax 监控 其中任何一个即可)。在error-report/src/plugins/errorReport.js中。

注释 Axios 异常监控,Axios异常 将不会被上报;

        // Axios 异常监控	
        axios.interceptors.response.use(null, error => {	
            this.options.msg = error.message;	
            this.options.stack = this.processStackMsg(error);	
            this.options.data = JSON.stringify({	
                category: "Axios"	
            });	

	
            // 合并上报的数据,包括默认上报的数据和自定义上报的数据	
            const reportData = Object.assign({}, this.options);	
            this.saveReport(reportData);	

	
            return Promise.reject(error);	
        });

或者注释掉原生的 Ajax 监控 ,原生Ajax异常 将不会被上报。

        // Ajax监控	
        const ajaxListener = {};	
        // 复制send方法	
        ajaxListener.tempSend = XMLHttpRequest.prototype.send;	
        // 复制open方法	
        ajaxListener.tempOpen = XMLHttpRequest.prototype.open;	
        // 重写open方法,记录请求的url	
        XMLHttpRequest.prototype.open = function(method, url, boolen) {	
            ajaxListener.tempOpen.apply(this, [method, url, boolen]);	
            this.ajaxUrl = url;	
        };	
        const self = this;	
        // 发送	
        XMLHttpRequest.prototype.send = function(data) {	
            const tempReadystate = this.onreadystatechange;	
            this.onreadystatechange = function() {	
                if (this.readyState === 4) {	
                    if (this.status >= 200 && this.status < 300) {	
                        tempReadystate && tempReadystate.apply(this, [data]);	
                        return;	
                    }	

	
                    self.options.msg = "AJAX 请求错误";	
                    self.options.stack = `错误码:${this.status}`;	
                    self.options.data = JSON.stringify({	
                        requestUrl: this.ajaxUrl,	
                        category: "XMLHttpRequest",	
                        text: this.statusText,	
                        status: this.status	
                    });	
                    // 合并上报的数据,包括默认上报的数据和自定义上报的数据	
                    const reportData = Object.assign({}, self.options);	
                    // 把错误信息发送给后台	
                    self.saveReport(reportData);	
                }	
            };	

	
            ajaxListener.tempSend.apply(this, [data]);	
        };


3.6、地址

GitHub:https://github.com/sky9102/error-report

NPM:https://www.npmjs.com/package/error-reports

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注我的官网 https://muyiy.cn,让我们成为长期关系

  3. 关注公众号「高级前端进阶」,每周重点攻克一个前端面试重难点,公众号后台回复「面试题」 送你高级前端面试题。

640?wx_fmt=png

猜你喜欢

转载自blog.csdn.net/github_34708151/article/details/99916632