likes
comments
collection
share

前端白屏:页面不翼而飞的那些事儿

作者站长头像
站长
· 阅读数 34

背景

Web页面白屏表示用户无法正常使用页面,可能会导致用户不耐烦并流失。作为开发者,应该在白屏出现后及时感知,迅速排查并修复问题

最近我在进行一些关于白屏问题的工作,简单地做一下总结。

白屏检测

所有工作的前提就是要知道页面白屏了,常见的白屏检测方式有:

方案原理优点缺点
监听 DOM 变化使用 Mutation Observer API 监听 关键 DOM 的变化开发成本低准确度度较低,无法检测未渲染和骨架屏的情况
根节点检测将 react、vue 等框架的渲染挂载到一个根节点上。检测根节点是否存在有效的子节点。开发成本低兼容性较差,只兼容如 react、vue 等主流框架
页面截图对比利用 canvas 做页面截图,检测截图的像素是否全部是白色技术栈无关,通用性好开发复杂,性能不高。非纯色背景难以正确判断
采样对比在页面的十字区域进行页面采样,使用 elementsFromPoint API 获取采样点下的 DOM 元素,判断采样 DOM 元素是否容器元素准确度高,技术栈无关,通用性好开发成本稍高

基于各方面的考虑,可以看出 采样对比 方案是最合适的。该方案的思路是在页面中间选取 17 个样本点形成十字形排列:

前端白屏:页面不翼而飞的那些事儿

在一段时间后判断这些采样点的最上层元素是否是容器元素,如['html', 'body', '#app']。如果所有采样点的最上层元素都是容器元素,则可以确定页面出现了白屏:

// 监听页面白屏
function whiteScreen() {
  // 页面加载完毕
  function onload(callback) {
    if (document.readyState === 'complete') {
      callback();
    } else {
      window.addEventListener('load', callback);
    }
  }
  // 定义外层容器元素的集合
  let containerElements = ['html', 'body', '#app', '#root'];
  // 容器元素个数
  let emptyPoints = 0;
  // 选中dom的名称
  function getSelector(element) {
    if (element.id) {
      return "#" + element.id;
    } else if (element.className) {// div home => div.home
      return "." + element.className.split(' ').filter(item => !!item).join('.');
    } else {
      return element.nodeName.toLowerCase();
    }
  }
  // 是否为容器节点
  function isContainer(element) {
    let selector = getSelector(element);
    if (containerElements.indexOf(selector) != -1) {
      emptyPoints++;
    }
  }
  onload(() => {
    // 页面加载完毕初始化
    for (let i = 1; i <= 9; i++) {
      let xElements = document.elementsFromPoint(window.innerWidth * i / 10, window.innerHeight / 2);
      let yElements = document.elementsFromPoint(window.innerWidth / 2, window.innerHeight * i / 10);
      isContainer(xElements[0]);
      // 中心点只计算一次
      if (i != 5) {
        isContainer(yElements[0]);
      }
    }
    // 17个点都是容器节点算作白屏
    if (emptyPoints == 17) {
      // 获取白屏信息
      console.log({
        status: 'error'
      });
    }
  }
}

白屏上报与告警

我们可以设定一个每秒轮询的机制,通过轮询处理器检测页面是否出现白屏。如果页面没有出现白屏,则停止轮询。如果出现白屏,我们可以在有效的白屏上报时间点上报白屏日志,例如在10秒、15秒、20秒时进行上报。

window.addEventListener("load", () => {
  // 白屏上报时机是10秒、15秒、20秒
  const intervals = [10, 15, 20];

  const maxSecond = 20;

  // 白屏上报的时间间隔,如果遇到一个非白屏的时间点,则不再上报
  const runTime = (frontSecond) => {
    setTimeout(() => {
      // 获取当前轮询时间
      const curSecond = frontSecond + 1;

      if (curSecond > maxSecond) {
        // 最后一个上报白屏时间触发,轮询结束
        onFinish?.();
        return;
      }

      // 判断页面是否白屏
      const isWhiteScreenFlag = isWhiteScreen(containerSelectors);

      if (isWhiteScreenFlag) {
        if (intervals.includes(curSecond)) {
          // 白屏触发并到了上报时机
          report(`白屏${curSecond}秒`);
        }

        runTime(curSecond);
      } else {
        // 页面非白屏,轮询结束
        onFinish?.();
      }
    }, 1000);
  };

  runTime(0);
});

白屏是一种较为严重的用户异常,因此最好对白屏日志配置告警。目前我配置的告警策略是:当用户出现白屏 10 秒时,触发告警:

前端白屏:页面不翼而飞的那些事儿

但是告警只能知道用户出现白屏,并不能知道为什么会出现白屏,因此我们还需要知道如何排查白屏问题。

白屏排查

排查白屏问题的基本思路是获取尽量多的上报的用户现场日志,并通过一些用户标志来串联这些现场数据

用户标志

上面的告警截图中的告警内容里面包含了 visitorIdvisitId 等字段,这些字段就是用户标志。每一个用户现场日志的上报都带上这些用户标志。用户标志最大的作用就是查看用户访问页面时的上下文:

  • visitId:用户首次访问网站时生成,再次刷新时将会更新。该字段可用于串联用户访问当前页面时的所有日志。
  • visitorId:于浏览器首次访问网站时生成,并存储在本地存储(localStorage)中。在后续页面刷新时会读取 localStorage 中的数据。该字段不会变化除非清除本地数据,可弥补 visitId 无法串联用户刷新页面前后日志的缺陷。
  • sessionId:存储在会话存储(sessionStorage)中的 id 字段。在同一标签页中,该 id 保持不变。
  • userId:与业务相关的用户 id。通过该 id 可以串联一些后台业务数据。

用户现场日志

有时我们需要排查一些开发者难以复现但用户偶尔出现的问题,白屏问题也算是此类问题。常规的解决方法包括尝试模拟用户环境以复现问题,或直接与用户联系并共享屏幕来进行排查,这些方法需要耗费大量时间和精力。

最理想的解决方案是通过完善的用户现场日志上报,快速准确定位问题所在

JS 错误上报

JS错误是导致白屏问题的主要原因,通过分析错误堆栈,我们可以轻松地发现大部分白屏问题的原因。

然而,通过window.onerror收集到的JS错误通常是一些难以理解或缺乏堆栈信息的错误。在这种情况下,我们需要依赖于日志上报来更好地理解和解决问题

日志上报

日志上报是排查白屏问题的另一种有效手段。基本思路是将页面划分为若干阶段,逐个阶段记录日志并将它们串联起来形成链路日志。当出现白屏问题时,通过过滤带有用户标志的日志上报数据,我们可以关注链路日志中出现的中断点,从而初步判断出代码可能出现问题的位置。

日志上报很考验前端工程师的经验和能力,因为在排查问题时往往需要根据经验猜测可能存在问题的地方。经验丰富的前端工程师通常能大致确定哪些地方需要进行日志上报,这可能包括:

  • html 开始加载成功
  • css 加载成功
  • index.js 加载成功
  • 后台接口请求成功
  • 图片请求成功
  • 各种点击事件

后台接口日志

白屏问题也可能是后台接口数据返回异常导致的,出现白屏问题时收集并分析后台接口数据也是一种排查手段。收集方案:

  • 方案一:前端生成一个traceId并将其传递给后台接口,后台通过这个traceId来串联完整的后台接口链路。在白屏问题出现时,可上报后台接口的URL和traceId。
  • 方案二:重写fetch函数,收集每个HTTP请求的请求参数、请求头、响应头和响应体。在白屏问题发生时,将这些信息进行上报。

方案一的优点是仅需上报traceId并利用后台日志,从而减少上报内容。然而,其缺点在于无法获取完整的HTTP信息,如无法收集到404、503等响应状态码。

方案二的优点在于能够上报完整的HTTP信息,但缺点在于需要修改fetch/axios接口,改造成本高,且上报的HTTP信息较多可能会造成上报资源的浪费。

性能数据

导致页面白屏的原因可能是页面加载过慢,查看性能数据可以排除用户网络是否真的很慢导致的问题。性能数据的收集可包括Web Vitals上报、Performance数据以及链路耗时的上报。

其他基本信息

此外,还可以收集以下基本信息:

  • 浏览器信息:包括浏览器类型、版本、用户代理(userAgent)等。
  • 页面信息:当前页面的URL、页面加载时间等。
  • 性能数据:涵盖内存使用情况、CPU利用率等。

总结

“养兵千日,用在一时”,白屏问题可能不经常出现,但一旦发生可能带来严重损失。若无完善的排查机制,则排查过程可能耗费大量时间和精力。进行白屏上报并非浪费时间,通常能帮助节省大量排查问题的时间。

参考文章

转载自:https://juejin.cn/post/7368118907901526053
评论
请登录