错误监控系统

自搭建错误监控文档

一般来说,我们之所以需要搭建前端监控体系,主要是为了解决两个问题。

  1. 如何及时发现问题?
  2. 如何快速定位并解决问题?

既然叫前端监控体系,那么我们到底需要监控些什么内容?对于线上的系统是否运行正常,我们可以通过以下信息进行观察,包括:

  • 页面的整体访问情况,包括常见的 PV、UV、用户操作行为;
  • 页面的性能情况,包括页面加载耗时、接口耗时等各项数据统计。

需要关注的数据类型

结合前面提到的页面的整体访问情况、页面的性能情况、用户问题定位等,我们可以将需要进行关注的数据分成五类。

  1. 系统的生命周期数据,可用于观察页面性能情况、整体访问情况等。
  2. HTTP 测速数据,可用于观察外部服务调用情况 、页面性能优化等。
  3. 系统异常数据,可用于观察系统稳定性、系统异常问题。
  4. 用户行为数据,可用于观察页面稳定性、整体访问情况等。
  5. 用户日志,用于进行用户反馈的问题排查。

生命周期数据

前端应用的生命周期指页面加载的关键时间点,通常包括页面打开、更新、关闭等耗时数据。我们可以通过PerformanceTiming属性中获取到一些生命周期相关的数据,比如:

  • 用于页面跳转:navigationStartunloadEventStart/unloadEventEnd等。
  • 用于页面加载:domLoadingdomInteractivedomContentLoadedEventStart/domContentLoadedEventEndloadEventStart/loadEventEnd等。

除此之外,我们还可以通过documentDOMContentLoadedreadystatechange等事件,来获取页面加载的关键点。

但随着前端框架的使用,页面的渲染过程、页面间的切换等逻辑都交给了框架进行控制,因此像DOMContentLoadedreadystatechange这些事件已经失去了原本的作用,很多时候我们会在框架本身提供的生命周期函数中进行数据的收集,比如在 Vue 中就有beforeCreate/createdbeforeMount/mountedbeforeUpdate/updatedbeforeDestroy/destroyed这些生命周期的钩子。

除了框架本身提供的生命周期以外,我们还可以使用 MutationObserver 接口,该接口提供了监听页面 DOM 树变化的能力,结合 performance 获取到具体的时间。

// 注册监听函数
 
const observer = new MutationObserver((mutations) => {
	console.log(`时间:${performance.now()},DOM树发生了变化!有以下变化类型:`);
 
	for (let i = 0; i < mutations.length; i++) {
		console.log(mutations[0].type);
	}
});
 
// 开始监听 document 的节点变化
 
observer.observe(document, {
	childList: true,
 
	subtree: true,
});

HTTP 测速数据

HTTP 请求相关的数据,同样可以通过 PerformanceTiming 属性获取,包括 HTTP 跳转开始/结束、域名查询开始/结束等各种时间戳。

系统异常数据

一般来说,脚本执行异常大多数情况下会直接导致功能不可用,因此首先需要关注系统异常的数据。

常见的前端异常包括:

  1. 逻辑错误,可理解为开发实现功能的时候,逻辑梳理不符合预期;
  2. 代码健壮性,可理解为代码边界情况考虑不周,异常逻辑执行出错;
  3. 网络错误,可理解为用户网络情况异常、后台服务异常等错误;
  4. 系统错误,可理解为代码运行环境兼容性问题导致出错;
  5. 页面内容异常,可理解为缺少内容、绑定事件异常、样式异常等

对于 1-4 的异常情况,可以使用 window.onerrordocument.addEventlistener(error)XMLHttpRequest status 等方法来进行拦截,同时可获取错误相关的信息和数据。比如,通过监听 window.onerror 事件,我们可以获取项目中的错误和分析堆栈,将错误信息自动上报到后台服务中。

对于第 5 项的页面内容异常,大多数情况并不会影响系统中大多数功能的运行,同时也缺少可直观观察的数据信息。因此一般情况下,可以通过回归测试、UI 界面测试等方式在上线前进行避免。

用户行为数据

我们还可以关注用户的一些行为数据,包括页面浏览量或点击量、用户在每一个页面的停留时间、用户通过什么入口来访问该页面、用户在页面中的一些操作行为。用户行为数据可以结合 DOM 元素的事件监听、页面的加载情况等方式来获取。

  • 这些行为数据的统计可以用来监控页面的功能是否正常
  • 通过这些用户行为数据,我们还可以统计出用户在时间轴上的操作顺序,以及每个步骤的操作时间、操作内容等,通过可视化系统直观地展示用户的链路情况,包括系统的入口来源、打开或关闭的页面、每个功能点的点击和操作时间、功能异常的情况 等
  • 用户链路相关信息还可以用来定位问题,比如配合用户日志进行分析。

用户日志

系统运行时输出的日志,可以通过两种方式存放。

  1. 上报到服务器。由于日志内容很多,如果全量上报到服务器会导致存储成本过大,同时频繁的上报也会增加接口的维护成本。除此之外,由于网络原因等还可能导致部分或全部的日志丢失等问题。
  2. 本地存储。该方案需要引导用户手动操作提交本地日志,或者通过服务端下发配置自动上传,才可以获取到日志内容,从而可以进行具体的问题定位。如果无法联系到用户,或是缓存被清理的情况下,则可能由于异常无法重现而无法修复。

数据埋点

数据埋点在业界中已经是比较成熟的解决方案,其中前端常见的埋点方案有三种:代码埋点、可视化埋点、无痕埋点。

不管使用哪种埋点方式,我们都需要对数据进行标准化处理。由于最终的数据需要落盘到服务端并进行计算和监控,因此我们需要将采集的数据,按照与服务端约定好的协议格式来进行转换。

同时,为了避免用户突然关闭应用、浏览器异常等情况导致数据的丢失,还可以配合本地缓存的方式,将数据进行缓存,在应用恢复的时候进行数据的上报。

数据上报

为了避免数据的上报过于频繁、增加服务端的压力,我们可以在本地进行数据的整合,比如通过队列或数组的方式进行维护,然后选择以下方式/时机进行上报。

定期/定量上报。

对于前端来说,过于频繁的请求可能会影响到用户其他正常请求的体验,因此通常我们需要将收集到的数据存储在本地。当收集到一定数量之后再打包一次性上报,或者按照一定的频率(时间间隔)打包上传,打包上传将多次数据合并为一次,可以减轻服务器的压力。

关键生命周期上报。

由于用户可能在使用过程中遇到异常,或者在使用过程中退出,因此我们还需要在异常触发的时候、用户退出程序前进行上传,以避免问题没能及时被发现和定位。

用户主动提交。

一些异常和使用体验问题,可以引导用户进行主动上传。当用户触发上传的操作后,可以将本地的数据和日志一并进行提交。

埋点上报方式

数据监控

数据上报完成后,一般来说需要搭建可视化的管理端,来对这些数据进行直观的监控。

在日常监控中,我们还会通过对监控数据、配置告警阈值等方式,结合邮件、机器人等方式推送到相关的人员,来及时发现并解决问题。

主要是因为监控的搭建比较简单,但维护是个大头,因此更多时候大家会把监控的维护放在可控范围内。

学习资料