搭建前端异常监控系统

  • 最近公司的业务系统和几个对外应用Web端进入了最后测试阶段,随之带来的问题是每次出现问题都要测试人员主动联系我们开发者,但有时又无法重现, 既浪费时间, 又无法及时有效解决异常。因此,我觉得搭建一个前端异常监控系统, 用来实时收集前端异常, 及时解决前端异常。

一、开源监控服务的优劣

在日常的工作中,我们在开发某个功能或者应用之前, 都会先搜寻一下是否有相似的开源项目,以此来提高开发效率。在前端监控系统这类项目中, 我比较喜欢Sentry这个项目:

1、功能齐全,包括多项目管理, 团队与成员的管理,可视化统计分析,issues分配等等。

2、SDK丰富, 包括了JavaScript, Nodejs, Java等等收集工具。

3、可以采用Docker安装,容器化管理数据。

4、项目一直在维护更新。

但功能齐全的背后带来的问题就是,每个公司都会有客制化的需求, 而要想在如此庞大的项目中二次开发是十分吃力的。对于我们公司目前的开发力量开始, 暂时无法分配人员专门客制化Sentry。

二、收集日志的方法

收集日志的手段,基本是两种,一个是逻辑中的错误判断,为主动判断;一个是利用语言给我们提供的捷径,暴力式获取错误信息,如try..catchwindow.onerror

1 上报Ajax请求错误

就公司目前的开发栈来说, 前后端通讯都是依靠rest api,尤其在使用React这种前端框架后, 在axios的请求中会出现无法预料的错误与异常。那么此时我们需要主动调用日志上报工具收集错误并发给监控系统。

在axios的响应拦截中(示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//响应拦截
axios.interceptors.response.use(
response => {

return response.data;

error => {
appStore.hideLoading();
Reporter.send({
{
error: error
msg: "ajax error"
}
})
return Promise.reject(error);
}
);

2、使用try..catch捕获

判断某个函数或某段代码里存在错误:

1
2
3
4
5
6
try {
fetch();

} catch(e){
Reporter.send(e);
}

在React应用中, 可以使用组件的 componentOnCatch来捕获该组件内所有错误

3、window.onerror

捕获全局错误

1
2
3
4
5
window.onerror =  () => {
var errInfo = format(arguments);
Reporter.send(errInfo);
return true;
};

在上面的函数中返回 return true,控制台不会输出错误信息。下面是它的参数信息:

1
2
3
4
5
6
7
8
9
10
/**
* @param {String} errorMessage 错误信息
* @param {String} scriptURI 出错的文件
* @param {Long} lineNumber 出错代码的行号
* @param {Long} columnNumber 出错代码的列号
* @param {Object} errorObj 错误的详细信息,Anything
*/
window.onerror = function(errorMessage, scriptURI, lineNumber,columnNumber,errorObj) {
// code..
}

三、前端上报异常的坑

1、无具体报错信息,Script error.

在日常开发中我们会医用一些第三方库, 当这些库在我们的应用中出现异常时, 由于浏览器的安全限制,我们无法捕获到具体的错误和错误位置,目的是避免数据泄露到不安全的域中。

2、 异常上报跨域限制

这也是由于浏览器的同源策略, 不予对非同源的资源进行操作。但通常异常监控系统与目标应用都是不同源, 这就涉及到一个跨域问题了。在React的开发中,若使用rest api进行通讯, 也需要配置反向代理才能进行Ajax请求。

那我们该如何在异常上报中打破跨域的限制呢?

在上报的HTTP的投中加入 header('Access-Control-Allow-Origin: *') 同样,在服务中也需要设置 Access-Control-Allow-Origin的响应头。

3、压缩代码无法定位到错误位置。

在我们的日常开发中,我们使用Debug可以定位到代码错误的哪一行哪一列。但在生产环境中的代码通常是经过压缩的,按照正常debug方法, 显示的错误位置都是第一行。那么我们如何解决呢?

第一个想到的办法是利用 sourceMap,利用它可以定位到压缩代码某一点在未压缩代码的具体位置。下面是 sourceMap 引入的格式,在代码的最后一行加入:

1
//# sourceMappingURL=index.js.map

4、切勿重复注册error事件

在以前的前端应用中,很容易出现这个问题。在React这种单页面应用, 我们只需要在应用的入口处注册我们的error事件即可。保证error事件只被注册一次, 以免漏掉错误发生点。

5、异常上报频率

我们当然希望把所有的错误上上报到监控系统中, 但一旦应用十分庞大时, 上报的频率会非常大, 在一定程度上会影响前端应用的性能。这个只能根据实际项目去衡量得失了。

四、设计监控系统

对于前端开发人员来说, 最熟悉的还是Nodejs了。因此我们决定使用Nodejs搭建一个简单的异常收集与监控系统。

1、数据库设计

在数据库想选择上当然是MongoDB了 , 适合存储格式不统一的日志文本。

创建四个文档:

####(1)user 用于存储监控系统的用户和角色等控制。

(2) bug 用于存储前端上报的错误与异常。

(3) project 目前来说我们的有不止一个的前端项目, 所有监控系统需要支持多项目管理, 存储不同的项目的上报KEY,用于统计和分析。

(4) ajax 用于ajax请求中上报的错误, ajax的错误或许会很多, 而且错误格式与window.error不一致, 所以将两者分开存储。

剩下的就是各个文档中的字段设计了, 这里就不赘述了。

2、系统架构设计

  • config 用于一些系统配置

  • controllers 用于系统控制器,接受http请求被返回处理后的结果

  • lib 用于系统函数和工具函数

  • models 用于数据模型的定于与设计

  • router 用于系统的路由定义

  • services 用于封装数据库的查询服务

  • app.js 是整个Nodejs系统的入口,启动http服务和连接数据库等等。

3、reast api设计

在rest通讯中最重要的一点就是使用token进行用户的认证与授权(JWT), 我在这里使用的是jsonwebtoken

在控制器的方法中定义http的请求的类型、参数和响应体,总体难度不大, 根据项目实际需求来设计。

五、 最后

到目前为止我们的简单异常监控系统初具雏形了,剩下的就是如何填充血肉了。在整个系统的设计中还是学到不少知识, 有些东西因为时间久了,有些遗忘,不得不去重新学习。因此,作为一个开发者, 最重要的还是要时刻保持求贤若渴的心态许学习心知识和温故旧知识。