Jansiel Notes

前端白屏检测:SDK的设计与实现

前言

前端白屏指页面在加载过程中长时间无法正常展示内容,内容区空白,使用户无法进行查看、保存等一切操作,这是非常严重的问题。如果能尽早检测到白屏问题,就可以及时处理,避免或降低负面影响。

白屏的检测手段有两种。一是真实用户端的检测,通过接入白屏检测SDK实现,无法在用户端白屏报错前发现问题,是被动监控的方式;另一种是自动化检测,在团队内部通过自动化工具模拟用户行为主动检测,可以提前发现问题。

本文为前端白屏检测的上篇,主要讲真实用户端的检测,即SDK的设计与实现。

白屏的表现与原因

白屏的通常表现为:

  1. 页面空白或仅显示背景色,没有实际内容

  2. 页面一直展示骨架屏,包括页面loading状态

  3. 页面只展示导航菜单,内容区空白,包括微前端或iframe嵌套子页面的场景

导致白屏的原因分两种:资源加载错误、代码执行错误。

检测方案对比

方案 原理 优点 缺点
检测根节点是否渲染 SPA框架渲染的 DOM 一般挂载在一个根节点下,监听onload、onerror事件,检测根节点下是否挂载 DOM 开发成本低 通用性差,只兼容主流 SPA 框架
监听 DOM 变化 利用 Mutation Observer API 监听DOM变化 开发成本低 准确度低,无法检测未渲染、始终渲染骨架屏等情况,如果用户长时间未操作DOM可能会误判白屏
页面截图对比 对页面截图,将截图与纯白的图片做对比 技术栈无关,通用性好 准确度低,无法检测纯背景色、骨架屏的白屏场景
前端框架内置ErrorBoundary组件捕获异常 利用ErrorBoundary组件捕获JS执行异常检测白屏 开发成本低 无法检测资源异常导致的白屏,只兼容于特定框架应用,接入时对业务代码侵入大
页面关键点采样对比 在页面中垂直/交叉取多个采样点,用 elementsFromPoint API 获取采样点下的 HTML 元素,判断采样点元素是否与容器元素相同 准确度高,技术栈无关,通用性好 开发成本稍高

通过以上对比发现,采用「页面关键点采样对比」的实现方案较好。

需要注意的是,对于主应用内嵌入的iframe的场景,因为每次采样取到的都是整个iframe元素,所以无法在主应用侧判断iframe是否白屏,需要在iframe应用内接入白屏检测SDK。

流程图

Jansiel_Essay_1702114766926

数据采集

屏幕采样点选取

采样点的选取有三种方式:垂直采样、交叉采样、垂直交叉采样。

垂直采样

Jansiel_Essay_1702114796870

交叉采样

Jansiel_Essay_1702114819898

垂直交叉采样

Jansiel_Essay_1702114843505
很明显,采样点越多判断越准确,但计算量稍大一点,不过我们利用requestIdleCallback在浏览器空闲时计算。因此,我们选择垂直交叉的采样方式。

白屏的判断标准与检测时机

有骨架屏和无骨架屏应用的检测方式不一样,检测时机也有细微差别。

无骨架屏场景

检测时机

  1. document.readyState在complete时或load事件触发时
  2. 全局error事件触发时
  3. 全局unhandledrejection事件触发时

检测方式

初始化SDK时,我们需要配置哪些是根容器,如果根容器为空则说明是白屏。

具体实现方式为,根据屏幕的宽度(window.innerWidth)和高度(window.innerHeight)算出每个采样点的具体坐标,再用elementsFromPoint获取每个坐标的 dom 元素,对比获取的元素是否为配置的根容器元素。

仔细想一下,上面的判断方式其实会有问题。

因为在 微前端与iframe场景 下,子应用白屏时,应该也需要上报才对。如果按上述方式判断,主应用(一般包含导航或者一级菜单)如果没有白屏,子应用永远不会被检测出白屏。因此,需要兼容此类场景。

兼容方式也很简单,我们只要判断内容区内的采样点满足白屏条件即可。大部分后台类的应用,会有顶部导航或左侧的一级菜单,因此我们选定右下方为内容区。

Jansiel_Essay_1702114870709

如上图所示,整个屏幕共33个采样点,其中内容区有28个。简单起见,检测白屏时,我们判断空白的采样点是否大于等于28个。采样点坐标的获取如下:

 1for (let i = 1; i <= 9; i++) {
 2  // x轴采样点
 3  const xElements = document?.elementsFromPoint((window.innerWidth * i) / 10, window.innerHeight / 2);
 4  // y轴采样点
 5  const yElements = document?.elementsFromPoint(window.innerWidth / 2, (window.innerHeight * i) / 10);
 6  // 上升的对角线采样点
 7  const upDiagonalElements = document?.elementsFromPoint(
 8    (window.innerWidth * i) / 10,
 9    (window.innerHeight * i) / 10,
10  );
11  // 下降的对角线采样点
12  const downDiagonalElements = document?.elementsFromPoint(
13    (window.innerWidth * i) / 10,
14    window.innerHeight - (window.innerHeight * i) / 10,
15  );
16
17  if (this.isContainer(xElements[0] as HTMLElement)) emptyPoints++;
18
19  // 中心点只计算一次
20  if (i !== 5) {
21    if (this.isContainer(yElements[0] as HTMLElement)) emptyPoints++;
22    if (this.isContainer(upDiagonalElements[0] as HTMLElement)) emptyPoints++;
23    if (this.isContainer(downDiagonalElements[0] as HTMLElement)) emptyPoints++;
24  }
25}
26

有骨架屏场景

检测时机

  1. document.readyState在complete之前

  2. 全局error事件触发时

  3. 全局unhandledrejection事件触发时

检测方式

如果应用内有骨架屏,继续用无骨架屏应用的白屏检测方式已经无法判断白屏,因为骨架屏也是有效的 dom 元素。

有骨架屏应用的检测方式为:对比初次采样前后获取的 dom 元素是否一致。因为在页面加载完成前可能已经渲染完骨架屏,为了获取对照组数据,初次采样的时间要在页面加载完成前。

 1// 项目有骨架屏
 2if (this.isSkeletonApp) {
 3  if (document.readyState !== 'complete') {
 4    this.idleCallback({
 5      type: 'beforeComplete',
 6      message: '骨架屏场景白屏',
 7    });
 8  }
 9} else {
10  // 页面加载完毕
11  if (document.readyState === 'complete') {
12    this.idleCallback({
13      type: 'complete',
14      message: '页面加载完毕白屏',
15    });
16  } else {
17    window.addEventListener(
18      'load',
19      this.idleCallback.bind(this, {
20        type: 'load',
21        message: '页面加载完毕白屏',
22      }),
23    );
24  }
25}
26
27window.addEventListener('error', (e) => {
28  this.idleCallback({
29    type: e.type,
30    message: e.message,
31    filename: e.filename,
32    lineno: e.lineno,
33    colno: e.colno,
34  });
35});
36
37window.addEventListener('unhandledrejection', (e) => {
38  this.idleCallback({
39    type: e.type,
40    reason: e.reason,
41    message: 'Promise未捕获的错误',
42  });
43});
44

数据上报

检测出白屏问题后,就要上报白屏信息到数据后台了。一般数据后台需要有数据清洗、存储、消费、告警等功能。此外,还需要区分不同的产品与环境,控制上报数据并发量、上报用户浏览器信息、用户行为数据、方便排查问题的Sourcemap,告警方式与规则等细节问题。如果要将数据后台做的全面细致,实现成本是比较高的。

权衡投入产出比后,我们的数据后台复用了云音乐部门同事研发的前端错误监控平台Corona。我们要做的就是将上报白屏错误到Corona的逻辑,内置到白屏检测SDK中。

SDK的接入方式

SDK支持以外链方式接入前端应用,除云音乐的Corona puzzle脚本外,不依赖其他资源加载,一般只需要改动模板文件,不侵入业务代码。

SDK API

配置项

字段名 类型 说明 是否必须 默认值
containers String[] 需要检测白屏的容器选择器列表 ['html', 'body', '#app', '#root', '#mainapp-container', '#subapp-container']
corona Corona Corona平台错误监控的实例 window.corona
isSkeletonApp Boolean 是否是有骨架屏的应用 false
autoInit Boolean 是否自动初始化SDK true
debug Boolean 是否开启调试模式,开启后会打印日志 false

方法

方法名 说明 参数 返回值
init 初始化SDK

通过外链接入

只需要在模板文件内,通过外链script引入白屏检测SDK及其依赖的Corona SDK即可。

总结

本文首先介绍了前端白屏表现、白屏原因,以及修复白屏问题的业务价值。然后对比几种常见的白屏检测方案,并介绍了采样点检测方案的具体实现,包括采样点如何选取、白屏的判断标准与检测时机、微前端与iframe场景的兼容等。

白屏检测工具完善现有的质量保障体系。使我们能尽早发现、及时处理白屏问题,减少线上重大故障几率,降低白屏问题对客户的负面影响。下一篇文章将介绍如何自动化的检测白屏,模拟用户的行为,主动发现问题。