从 0 到 1 搭建前端异常监控系统

本篇文章读后,你将GET的技能: javascript

●收集前端错误(原生、React、Vue)html

●编写错误上报逻辑前端

●利用Egg.js编写一个错误日志采集服务vue

●编写webpack插件自动上传sourcemapjava

●利用sourcemap还原压缩代码源码位置 react

●利用Jest进行单元测试 webpack

有没有心动的感受?赶忙学起来吧!程序员

如何捕获异常

JS异常:

js异常的特色是,出现不会致使JS引擎崩溃,最多只会终止当前执行的任务。web

好比一个页面有两个按钮,若是点击按钮致使页面发生异常,这个时候页面不会崩溃。ajax

只是这个按钮的功能失效,其余按钮还会有效☟

上面的例子咱们用setTimeout分别启动了两个任务。

虽然第一个任务执行了一个错误的方法。程序执行中止了。可是另一个任务并无收到影响。

其实若是你不打开控制台都看不到发生了错误。好像是错误是在静默中发生的。

下面咱们来看看这样的错误该如何收集。

try-catch:

JS做为一门高级语言咱们首先想到的使用try-catch来收集。

若是在函数中错误没有被捕获,错误会上抛。

image.png

控制台中打印出的分别是错误信息和错误堆栈。

读到这里你们可能会想那就在最底层作一个错误try-catch不就行了吗。

确实做为一个从java转过来的程序员也是这么想的。

可是理想很丰满,现实很骨感。咱们看看下一个例子。

image.png

你们注意运行结果,异常并无被捕获。

这是由于JS的try-catch功能很是有限一遇到异步就很差用了。

那总不能为了收集错误给全部的异步都加一个try-catch吧,太坑爹了。

其实你想一想异步任务其实也不是由代码形式上的上层调用的就好比本例中的setTimeout。

你们想一想eventloop就明白啦,其实这些异步函数都是就比如一群没娘的孩子出了错误找不到家大人。

固然我也想过一些黑魔法来处理这个问题好比代理执行或者用过的异步方法。

算了仍是仍是再看看吧。

异常任务捕获

window.onerror:

window.onerror 最大的好处就是同步任务、异步任务均可捕获。

image.png

onerror返回值

onerror还有一个问题你们要注意 若是返回true 就不会被上抛了。

否则控制台中还会看到错误日志。

监听error事件:

文件中的位置☟

window.addEventListener('error',() => {})

其实 onerror 当然好可是仍是有一类异常没法捕获。这就是网络异常的错误。

好比下面的例子。

<img src="./xxxxx.png">

试想一下咱们若是页面上要显示的图片忽然不显示了,而咱们浑然不知那就是麻烦了。

addEventListener就是☟

运行结果以下☟

Promise异常捕获:

Promise 的出现主要是为了让咱们解决回调地域问题。基本是咱们程序开发的标配了。

虽然咱们提倡使用 es7 async/await 语法来写。

可是不排除不少祖传代码仍是存在Promise写法。

new Promise((resolve, reject) => {
  abcxxx()
});

这种状况不管是onerror仍是监听错误事件都是没法捕获的。

image.png

除非每一个Promise都添加一个catch方法。

但显然,咱们不能这样作。

window.addEventListener("unhandledrejection", e => {
 console.log('unhandledrejection',e)
});

咱们能够考虑将unhandledrejection事件捕获的错误抛出交由错误事件统一处理就能够了。

async/await异常捕获:

实际上async/await语法本质仍是Promise语法。

区别就是async方法能够被上层的try/catch捕获。

image.png

若是不去捕获的话就会和Promise同样,须要用unhandledrejection事件捕获。

这样的话咱们只须要在全局增长unhandlerejection就行了。

image.png

小结:

实际上咱们能够将unhandledrejection事件抛出的异常再次抛出就能够统一经过error事件进行处理了。

最终用代码表示以下:

前端工程化

Webpack工程化:

如今是前端工程化的时代,工程化导出的代码通常都是被压缩混淆后的。

好比:

setTimeout(() => {
    xxx(1223)
}, 1000)

image.png

出错的代码指向被压缩后的JS文件,而JS文件长下图这个样子。

image.png

若是想将错误和原有的代码关联起来,那就须要sourcemap文件的帮忙了。

sourceMap是什么?

简单说,sourceMap就是一个文件,里面储存着位置信息。

仔细点说,这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。

那么如何利用sourceMap还原异常代码发生的位置这个问题,咱们到异常分析这个章节再讲。

VUE 工程

利用vue-cli工具直接建立一个项目。

image.png

为了测试的须要咱们暂时关闭eslint 这里面仍是建议你们全程打开eslint。

在vue.config.js进行配置

image.png

咱们故意在(文件位置☟)

src/components/HelloWorld.vue

这个时候 错误会在控制台中被打印出来,可是错误事件并无监听到。

errorHandle 句柄:

为了对Vue发生的异常进行统一的上报,须要利用vue提供的errorHandle句柄。

一旦Vue发生异常都会调用这个方法。

咱们在src/main.js

image.png

React 工程:

npx create-react-app react-sample

cd react-sample

yarn start

咱们用useEffect hooks 制造一个错误:

而且在src/index.js中增长错误事件监听逻辑:

window.addEventListener('error', args => {
    console.log('error', error)
})

可是从运行结果看虽然输出了错误日志可是仍是服务捕获。

Error Boundary 组件

错误边界仅能够捕获其子组件的错误。

错误边界没法捕获其自身的错误。

若是一个错误边界没法渲染错误信息,则错误会向上冒泡至最接近的错误边界。

这也相似于 JavaScript 中 catch {} 的工做机制。

建立ErrorBoundary组件

在src/index.js中包裹App标签☟

最终运行的结果:

异常上报如何选择通信方式

动态建立img标签:

其实上报就是要将捕获的异常信息发送到后端。最经常使用的方式首推进态建立标签方式。

由于这种方式无需加载任何通信库,并且页面是无需刷新的。

基本上目前包括百度统计 Google统计都是基于这个原理作的埋点。

new Image().src = 'http://localhost:7001/monitor/error'+ '?info=xxxxxx'

经过动态建立一个img,浏览器就会向服务器发送get请求。

能够把你须要上报的错误数据放在querystring字符串中,利用这种方式就能够将错误上报到服务器了。

Ajax上报:

实际上咱们也能够用ajax的方式上报错误,这和咱们在业务程序中并无什么区别。

上报哪些数据:

上报哪些数据:

咱们先看一下error事件参数:

其中核心的应该是错误栈,其实咱们定位错误最主要的就是错误栈。

错误堆栈中包含了绝大多数调试有关的信息。其中包括了异常位置(行号,列号),异常信息

上报数据序列化:

因为通信的时候只能以字符串方式传输,咱们须要将对象进行序列化处理。

大概分红如下三步:

一、将异常数据从属性中解构出来,存入一个JSON对象

二、将JSON对象转换为字符串

三、将字符串转换为Base64

固然在后端也要作对应的反向操做 这个咱们后面再说。

image.png

异常上报的后端服务器

搭建egg.js工程:

异常上报的数据必定是要有一个后端服务接收才能够。

咱们就以比较流行的开源框架eggjs为例来演示

# 全局安装egg-cli
npm i egg-init -g 

# 建立后端项目
egg-init backend --type=simple

cd backend
npm i

# 启动项目
npm run dev

编写error上传接口:

首先在app/router.js添加一个新的路由

image.png

建立一个新的:

controller (app/controller/monitor)

image.png

看一下接收后的结果 ☟

image.png

记入日志文件:

下一步就是将错误记入日志。实现的方法能够本身用fs写,也能够借助log4js这样成熟的日志库。

固然在eggjs中是支持咱们定制日志那么就用这个功能定制一个前端错误日志好了。

在/config/config.default.js中增长一个定制日志配置

image.png

在/app/controller/monitor.js中添加日志记录:

image.png

最后实现的效果:

image.png

Webpack插件实现SourceMap上传

谈到异常分析最重要的工做实际上是将webpack混淆压缩的代码还原。

建立Webpack插件:

/source-map/plugin(文件位置)

加载webpack插件:

webpack.config.js(文件位置)

添加sourceMap读取逻辑:

在apply函数中增长读取sourcemap文件的逻辑

/plugin/uploadSourceMapWebPlugin.js

实现http上传功能:

服务器端添加上传接口:

/backend/app/router.js(文件位置)

image.png

添加sourcemap上传接口:

/backend/app/controller/monitor.js

image.png

最终效果:

执行webpack打包时调用插件sourcemap被上传至服务器。

image.png

解析ErrorStack

考虑到这个功能须要较多逻辑,咱们准备把他开发成一个独立的函数而且用Jest来作单元测试:

先看一下咱们的需求☟

image.png

搭建Jest框架:

image.png

首先建立一个/utils/stackparser.js文件☟

image.png

在同级目录下建立测试文件stackparser.spec.js

以上需求咱们用Jest表示就是

image.png

整理以下:

下面咱们运行Jest

npx jest stackparser --watch

image.png

显示运行失败,缘由很简单由于咱们尚未实现对吧。

下面咱们就实现一下这个方法

反序列Error对象:

首先建立一个新的Error对象 将错误栈设置到Error中。

而后利用error-stack-parser这个npm库来转化为stackFrame

image.png

运行效果以下☟

image.png

解析ErrorStack:

下一步咱们将错误栈中的代码位置转换为源码位置

image.png

咱们再用Jest测试一下☟

image.png

这时咱们再看一下结果:

image.png

这样一来测试就经过啦~

将源码位置记入日志:

image.png

记录完成后,咱们再来看一下运行效果:

image.png

结束了这一步,咱们的ErrorStack工做就完成了。

须要运用的两种开源框架

Fundebug:

Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 

自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、荔枝FM、掌门1对一、核桃编程、微脉等众多品牌企业。

Sentry:

Sentry 是一个开源的实时错误追踪系统,能够帮助开发者实时监控并修复异常问题。

它主要专一于持续集成、提升效率而且提高用户体验。

Sentry 分为服务端和客户端 SDK,前者能够直接使用它家提供的在线服务,也能够本地自行搭建;

后者提供了对多种主流语言和框架的支持,包括 React、Angular、Node、Django、RoR、PHP、Laravel、Android、.NET、JAVA 等。

同时它可提供了和其余流行服务集成的方案,例如 GitHub、GitLab、bitbuck、heroku、slack、Trello 等。

目前公司的项目也都在逐步应用上 Sentry 进行错误日志管理。

总结:

截止到目前为止,咱们把前端异常监控的基本功能算是造成了一个MVP(最小化可行产品)。

后面须要升级的还有不少,对错误日志的分析和可视化方面可使用ELK。

发布和部署能够采用Docker。对eggjs的上传和上报最好要增长权限控制功能。

相关文章
相关标签/搜索