从0到1搭建前端异常监控系统(Vue + Webpack + Node.js + Egg.js + Jest)(上)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从0到1搭建前端异常监控系统(Vue + Webpack + Node.js + Egg.js + Jest)

您将Get的技能


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


  • 编写错误上报逻辑


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


  • 编写webpack插件自动上传sourcemap


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


  • 利用Jest进行单元测试


工作流程


  1. 收集错误


  1. 上报错误


  1. 代码上线打包将sourcemap文件上传至错误监控服务器


  1. 发生错误时监控服务器接收错误并记录到日志中


  1. 根据sourcemap和错误日志内容进行错误分析


一、 前端异常监控平台解决的问题


前端异常监控系统就是为了解决前端系统上线后的稳定性问题。一旦前端系统上线,发生运行异常造成页面阻塞,操作不流畅甚至无法打开网页的状况。我们需要通过技术手段收集、上报、分析异常才能保证前端项目稳定运行。


二、异常收集


首先先看看如何捕获异常。


1.  JS异常


js异常的特点是,出现不会导致JS引擎崩溃 最多只会终止当前执行的任务。比如一个页面有两个按钮,如果点击按钮发生异常页面,这个时候页面不会崩溃,只是这个按钮的功能失效,其他按钮还会有效。


setTimeout(() => {
  console.log('1->begin')
  error
  console.log('1->end')
})
setTimeout(() => {
  console.log('2->begin')
  console.log('2->end')
})


网络异常,图片无法展示
|


上面的例子我们用setTimeout分别启动了两个任务,虽然第一个任务执行了一个错误的方法。程序执行停止了。但是另外一个任务并没有收到影响。


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


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


1.1 try-catch


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


setTimeout(() => {
  try {
    console.log('1->begin')
    error
    console.log('1->end')
  } catch (e) {
    console.log('catch',e)
  }
})


网络异常,图片无法展示
|


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


function fun1() {
  console.log('1->begin')
  error
  console.log('1->end')
}
setTimeout(() => {
  try {
    fun1()
  } catch (e) {
    console.log('catch',e)
  }
})


网络异常,图片无法展示
|


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


读到这里大家可能会想那就在最底层做一个错误try-catch不就好了吗。确实作为一个从java转过来的程序员也是这么想的。但是理想很丰满,现实很骨感。我们看看下一个例子。


function fun1() {
  console.log('1->begin')
  error
  console.log('1->end')
}
try {
  setTimeout(() => {
    fun1()
  })
} catch (e) {
  console.log('catch', e)
}


网络异常,图片无法展示
|


大家注意运行结果,异常并没有被捕获。


这是因为JS的try-catch功能非常有限一遇到异步就不好用了。那总不能为了收集错误给所有的异步都加一个try-catch吧,太坑爹了。其实你想想异步任务其实也不是由代码形式上的上层调用的就比如本例中的settimeout。大家想想eventloop就明白啦,其实这些一步函数都是就好比一群没娘的孩子出了错误找不到家大人。当然我也想过一些黑魔法来处理这个问题比如代理执行或者用过的异步方法。算了还是还是再看看吧。


1.2 window.onerror


window.onerror 最大的好处就是可以同步任务还是异步任务都可捕获。


function fun1() {
  console.log('1->begin')
  error
  console.log('1->end')
}
window.onerror = (...args) => {
  console.log('onerror:',args)
}
setTimeout(() => {
  fun1()
})


网络异常,图片无法展示
|


  • onerror返回值


onerror还有一个问题大家要注意 如果返回返回true 就不会被上抛了。不然控制台中还会看到错误日志。


1.3 监听error事件


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


其实onerror固然好但是还是有一类异常无法捕获。这就是网络异常的错误。比如下面的例子。


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


试想一下我们如果页面上要显示的图片突然不显示了,而我们浑然不知那就是麻烦了。

addEventListener就是


window.addEventListener('error', args => {
    console.log(
      'error event:', args
    );
    return true;
  }, 
  true // 利用捕获方式
);


运行结果如下:


网络异常,图片无法展示
|


1.4 Promise异常捕获


Promise的出现主要是为了让我们解决回调地域问题。基本是我们程序开发的标配了。虽然我们提倡使用es7 async/await语法来写,但是不排除很多祖传代码还是存在Promise写法。


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


这种情况无论是onerror还是监听错误事件都是无法捕获的


new Promise((resolve, reject) => {
  error()
})
// 增加异常捕获
  .catch((err) => {
  console.log('promise catch:',err)
});


除非每个Promise都添加一个catch方法。但是显然是不能这样做。


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


网络异常,图片无法展示
|


我们可以考虑将unhandledrejection事件捕获错误抛出交由错误事件统一处理就可以了


window.addEventListener("unhandledrejection", e => {
  throw e.reason
});


1.5 async/await异常捕获


const asyncFunc = () => new Promise(resolve => {
  error
})
setTimeout(async() => {
  try {
    await asyncFun()
  } catch (e) {
    console.log('catch:',e)
  }
})


实际上async/await语法本质还是Promise语法。区别就是async方法可以被上层的

try/catch捕获。


网络异常,图片无法展示
|


如果不去捕获的话就会和Promise一样,需要用unhandledrejection事件捕获。这样的话我们只需要在全局增加unhandlerejection就好了。


网络异常,图片无法展示
|


小结


异常类型 同步方法 异步方法 资源加载 Promise async/await
try/catch ✔️ ✔️
onerror ✔️ ✔️
error事件监听 ✔️ ✔️ ✔️
unhandledrejection事件监听 ✔️ ✔️


实际上我们可以将unhandledrejection事件抛出的异常再次抛出就可以统一通过error事件进行处理了。


最终用代码表示如下:


window.addEventListener("unhandledrejection", e => {
  throw e.reason
});
window.addEventListener('error', args => {
  console.log(
    'error event:', args
  );
  return true;
}, true);


2. Webpack工程化项目中捕获异常


现在是前端工程化的时代,工程化导出的代码一般都是被压缩混淆后的。


比如:


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


网络异常,图片无法展示
|


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


网络异常,图片无法展示
|


如果想将错误和原有的代码关联起来就需要sourcemap文件的帮忙了。


sourceMap是什么


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


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


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


3. Vue捕获异常


3.1 创建工程


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


# 安装vue-cli
npm install -g @vue/cli
# 创建一个项目
vue create vue-sample
cd vue-sample
npm i
// 启动应用
npm run serve


为了测试的需要我们暂时关闭eslint 这里面还是建议大家全程打开eslint


在vue.config.js进行配置


module.exports = {   
  // 关闭eslint规则
  devServer: {
    overlay: {
      warnings: true,
      errors: true
    }
  },
  lintOnSave:false
}


我们故意在src/components/HelloWorld.vue


<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  mounted() {
    // 制造一个错误
    abc()
  }
};
</script>
```html
然后在src/main.js中添加错误事件监听
```js
window.addEventListener('error', args => {
  console.log('error', error)
})


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


网络异常,图片无法展示
|


3.2 handleError


为了对Vue发生的异常进行统一的上报,需要利用vue提供的handleError句柄。一旦Vue发生异常都会调用这个方法。


我们在src/main.js


Vue.config.errorHandler = function (err, vm, info) {
  console.log('errorHandle:', err)
}


运行结果结果:


网络异常,图片无法展示
|


4. React捕获异常


4.1 创建项目


npx create-react-app react-sample
cd react-sample
yarn start


我们l用useEffect hooks 制造一个错误


import React ,{useEffect} from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
  useEffect(() => { 
    // 发生异常
    error()
  }); 
  return (
    <div className="App">
      // ...略...
    </div>
  );
}
export default App;


并且在src/index.js中增加错误事件监听逻辑


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


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


网络异常,图片无法展示
|


相关实践学习
日志服务之数据清洗与入湖
本教程介绍如何使用日志服务接入NGINX模拟数据,通过数据加工对数据进行清洗并归档至OSS中进行存储。
相关文章
|
3天前
|
JavaScript
Vue 开发中的一些问题简单记录,Cannot find module ‘webpack/lib/RuleSet‘
Vue 开发中的一些问题简单记录,Cannot find module ‘webpack/lib/RuleSet‘
8 1
|
5天前
|
JavaScript 前端开发 CDN
前端 JS 经典:package.json 属性详解
前端 JS 经典:package.json 属性详解
9 1
|
2天前
|
前端开发 JavaScript
js 打开资源管理器(经典范例:纯前端选择并预览图片)
js 打开资源管理器(经典范例:纯前端选择并预览图片)
14 0
AxiosError: Network Error at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axio
AxiosError: Network Error at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axio
|
3天前
|
开发框架 监控 JavaScript
企业级node.js开发框架 【egg.js】 实用教程
企业级node.js开发框架 【egg.js】 实用教程
7 0
|
4天前
|
JavaScript 前端开发 应用服务中间件
|
5天前
|
JavaScript 前端开发 API
JS案例:前端Iframe及Worker通信解决思路
JS案例:前端Iframe及Worker通信解决思路
|
5天前
|
JavaScript 前端开发
JS进阶篇(前端面试题整合)(三)
JS进阶篇(前端面试题整合)(三)
12 0
|
5天前
|
前端开发 JavaScript
前端 JS 经典:箭头函数的意义
前端 JS 经典:箭头函数的意义
7 0
|
5天前
|
前端开发 JavaScript
前端 JS 经典:变量交换
前端 JS 经典:变量交换
6 0