H5白屏异常监控方案

2,411 阅读4分钟

背景

白屏是一种很糟糕的体验,而有些白屏是出现在特定场景、特定浏览器下,有时难以在测试和体验回归阶段发现,这时白屏监控就体现了价值。如果出现白屏,我们能够监控并上报,在配合前端其他日志进行分享,或许就能排除出用户出问题的点在哪里,并能解决。 那么H5页面如何做白屏监控呢?我们先搞明白H5页面一般都在什么情况下出现白屏。

什么情况下会出现白屏

资源加载错误

当资源尤其是JS资源加载异常时,最典型的例子莫过于 React、Vue 等 SPA 框架构建的 Web 应用,一旦 [bundle|app].js 因为网络原因访问失败,便会引发页面白屏。

这类白屏暂时可以先不用考虑

代码执行异常

JS语法使用不当

在低版本(iOS 8,9系统)的iOS或Android(4.4以下)上不支持,导致页面显示白屏,如:

  • 1.使用了es6的语法(如 let ),但是本身代码没有做es5的自动适配
  • 2.引入了三方外部插件,外部插件语法存在兼容性问题

页面逻辑问题

  • 读取 undefined null 的属性,null.a;
  • 对普通对象进行函数调用,const o = {}; o();
  • 将 null undefined 传递给 Object.keys,Object.keys(null);
  • JSON 反序列化接受到非法值,JSON.parse({});

接口异常导致的白屏

页面数据依赖网络接口,且页面没有默认的初始数据。导致在网络不好的情况下,接口数据没有获取到,从而导致页面列表数据空白等问题。

出现上述错误时也不一定就出现白屏,那要怎么判断是否白屏呢?

如何判断是否白屏

第一步:判断主元素是否渲染

判断主元素是否渲染,传入主元素数组,当未传入主元素时,可以把class为 core 的元素作为主元素

判断标识

  1. 传入的hostElement元素
  2. 当未传入hostElement时,以 class="core" 为标识
cherryApm.init({
    // 没有配置默认值使用class为 core 的元素
    hostElement: ['#id', '.className'] 
}, Tracker)

并非所有的项目都有传入了主元素,主元素没有也不代表真的白屏,这只是我们判断条件的第一步。

第二步:判断是否为ErrorBoundaries兜底组件

现在的前端项目当应用出现问题时,往往会在框架层面进行兜底展示,其实兜底页面也是一种白屏,也需要进行监控。

自 React 16 开始,任何未被错误边界捕获的错误将会卸载整个 React 组件树。

错误边界是 React 组件,它可以 在子组件树的任何位置捕获 JavaScript 错误,记录这些错误,并显示一个备用 UI ,而不是使整个组件树崩溃。

所以使用React 兜底组件可以避免白屏,兜底组件可以在整个H5统一使用同一个 class="error-boundary"的标识。

当判断有 class="error-boundary" 时也作为白屏进行上报,但在字段上报上需要做特殊标识,以区别正在的白屏

第三步:判断屏幕元素点是否为白屏元素

当没有主元素(没有hostElement和.core),也没用兜底组件时,可以使用 elementsFromPoint api 判断屏幕中的这 9 个点最上层元素是否都是白屏元素(根元素、容器元素)

如果选中的9个点中有7个点都是白屏元素,则说明这些地方都没有渲染其他元素,界面展示是白屏。

image.png

参考代码:

// 监听页面白屏
export function blankScreen() {
    // 页面加载完毕
    function onload(callback) {
        if (document.readyState === 'complete') {
            callback();
        } else {
            window.addEventListener('load', callback);
        }
    }
    // 定义属于白屏元素的白屏点
    let wrapperElements = ['html', 'body', '#container', '.content'];
    // 白屏点个数
    let emptyPoints = 0;
    // 选中dom点的名称
    function getSelector(element) {
        if (element.id) {
            return "#" + element.id;
        } else if (element.className) {// a b c => .a.b.c
            return "." + element.className.split(' ').filter(item => !!item).join('.');
        } else {
            return element.nodeName.toLowerCase();
        }
    }
    // 是否是白屏点判断
    function isWrapper(element) {
        let selector = getSelector(element);
        if (wrapperElements.indexOf(selector) != -1) {
            emptyPoints++;
        }
    }
    // 页面加载完毕初始化
    onload(function () {
        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);
            isWrapper(xElements[0]);
            isWrapper(yElements[0]);
        }
        // 总共9个点超过7个点算作白屏
        if (emptyPoints >= 7) {
            let centerElements = document.elementsFromPoint(
                window.innerWidth / 2, window.innerHeight / 2
            );
            console.log('页面白屏',{
                kind: 'stability',
                type: 'blank',
                emptyPoints,
                screen: window.screen.width + "X" + window.screen.height,
                viewPoint: window.innerWidth + "X" + window.innerHeight,
                selector: getSelector(centerElements[0])
            });
        }
    });
}

知道了什么情况下可能出现白屏以及如何判断是否白屏,但是要在什么时机触发判断白屏呢?

什么时机进行判断

触发onerror时

大多数情况下出现白屏都是因为代码执行异常所导致,那我们可以在代码异常的监控中来判断是否白屏。

// JS错误
window.onerror = function (message, source, lineno, colno, error): void {
    checkWhiteScreen()            
}
// Promise错误
window.addEventListener('unhandledrejection', function (event) {
    checkWhiteScreen()       
})

定时3秒后进行检查

有时JS代码并没有报错,但是因接口数据没有活动到,前端也没有设置默认数据时,也会出现白屏,此时没有onerror触发,可以通过设置定时器触发白屏检测。

const timer = setTimeout(() => {
    clearTimeout(timer)
    checkWhiteScreen()    
}, 3000)

当以上两种时机触发时,就可以去执行判断白屏的逻辑,当发现判断白屏的三个条件都成立,这判定为白屏,就可进行上报。