H5白屏异常监控方案
背景
白屏是一种很糟糕的体验,而有些白屏是出现在特定场景、特定浏览器下,有时难以在测试和体验回归阶段发现,这时白屏监控就体现了价值。如果出现白屏,我们能够监控并上报,在配合前端其他日志进行分享,或许就能排除出用户出问题的点在哪里,并能解决。 那么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 的元素作为主元素
判断标识
- 传入的
hostElement
元素 - 当未传入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个点都是白屏元素,则说明这些地方都没有渲染其他元素,界面展示是白屏。
参考代码:
// 监听页面白屏
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)
当以上两种时机触发时,就可以去执行判断白屏的逻辑,当发现判断白屏的三个条件都成立,这判定为白屏,就可进行上报。
转载自:https://juejin.cn/post/7178796635962408997