如何从零搭建一个监控系统

1,346 阅读4分钟

前言

       最近一直在考虑做前端工程化的个人项目,考虑到整个前端开发链路上的各个环节,这其中包含很多可以工程化的流程,例如自动化测试、性能监测、自动化部署以及今天要讲的自动化监控平台。

一. 为什么要做监控平台

       项目上线,总是能遇到一些匪夷所思的线上bug,然后这些问题还不好重现,需要通过客服去了解用户的机型、网络状况甚至操作流程,然后通过找到相同的机型想还原错误场景,最后发现完全没有问题!遇到这种情况,真的就是叫天天不应,叫地地不灵了~To B的项目,这种情况只能派内部工程师出差去客户现场解决问题,而To C的项目,然用户换个浏览器或者手机先试试,但是bug最终还是需要被解决掉的。线上的bug是最需要及时响应并解决掉的问题,但是作为开发者基于现有的条件根本无法做到快速定位问题所在,因此这也凸显出开发流程中有一套好的线上监控系统的重要性。

二. 监控系统架构

整体架构

如上图所示,整个系统分为三个流程:

  1. 监控的SDK:系统的核心组成,主要包含错误收集、数据处理和信息上报三个模块;
  2. Server端数据支撑:接收标准化错误信息数据的收集和数据存储,并做持久化处理,同时要包含预警通知系统;
  3. 可视化平台:错误信息的可视化展示,方便更清晰的查看和总结。

三. 前端异常收集

* 前端的异常包含很多种类,从源代码的语法错误、编译运行错误,再到AJAX请求报错、静态资源加载异常,任何一个不可控的前端异常,都能影响用户的使用体验,因此总结一下针对不同类型的异常的收集和处理:

DNS劫持

一般没有部署https的网站可能会发生DNS劫持的现象,而HTTP域下的劫持检测,其检测思路为请求Node层指定域名下的样本HTML或JavaScript资源,对比返回结果是否符合预期。针对这种异常,最好的处理方式就是网站部署HTTPS。

JS语法错误,运行异常

JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror(),监听全局的`onerror`事件

window.error = function (message, source, lineno, colno, error) {   /* TO DO */
  return true // 返回true,异常不会继续向上抛出,即阻止执行默认事件处理函数
}

但是问题在于`window.error`只能捕获运行时的同步错误,对于语法错误、资源加载异常和异步错误都无法捕获,因此需要其他的方式来处理这类异常。

静态资源的加载异常

对于图片或者其他CSS、JS静态资源,一旦加载错误,就会触发该元素的error事件,但是error事件并不会冒泡到window上面,因此只能在捕获阶段捕获这种类型的错误,目前Firefox浏览器上是可以通过`window.addEventListener`进行捕获:

window.addEventListener('error', function (err) {
 /* TO DO */
}, true) // true为捕获阶段

AJAX请求异常

所有的ajax请求库都是基于`XMLHttpRequest`进行二次封装的,因此通过拦截原型上面的`open`和`send`方法,根据`status`判断异常请求进行处理:

window.XMLHttpRequest.prototype.send = function () {/* TO DO */}
window.XMLHttpRequest.prototype.open = function () {/* TO DO */}

Promise异常捕获

针对Promise种reject之后没有被catch的错误,有原生的事件可以支持捕获相应的错误:

window.addEventListener('unhandledrejection', function (e) {
 /* TO DO */
 e.preventDefault() // 需要阻止事件冒泡,拦截默认的事件处理器
})

script error跨域脚本异常

针对这种脚本报错,一般都为跨域脚本,因此需要在对应的`script`标签加上`crossorigin`属性,允许脚本跨域访问:

<script src="demo.js" crossorigin></script>

同时服务器端的请求头需要设置`Access-Control-Allow-Origin`,设置完成之后跨域脚本的异常就可以按照正常的JS一样被捕获。

页面崩溃或者异常

用于代码错误,导致运行时出现堆栈溢出,进而引发页面崩溃,此时JS都无法正常运行,需要通过下面的方式进行捕获:

window.addEventListener('load', function () {  // 页面加载的事件  sessionStorage.setItem('good_exit', 'pending');  setInterval(function () {     sessionStorage.setItem('time_before_crash', new Date().toString());  }, 1000);});window.addEventListener('beforeunload', function () { // 页面正常关闭卸载的事件  sessionStorage.setItem('good_exit', 'true');});if(sessionStorage.getItem('good_exit') &&  sessionStorage.getItem('good_exit') !== 'true') {  /*     insert crash logging code here */  alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));}

如上述代码所示,在页面正常加载访问的时候,记录一个sessionStorage状态用于记录当前页面的加载状态`pending`,用户正常退出更新这个状态为`true`,如果这个页面发生崩溃,则不会被`beforeunload`事件监听到,则再次进入访问原状态仍为`pending`,就可以判断页面发生崩溃。

未完待续......后面会更新,项目设计、打包工具和代码编写相关部分~

参考链接:

  1. 如何优雅处理前端错误
  2. 监控平台前端SDK开发实践