7、页面奔溃

最后更新于:2022-04-02 06:10:55

  页面奔溃包含两种场景,第一种是浏览器在加载网页时遇到问题导致的奔溃,另一种是因为脚本渲染出错导致页面空白无内容的奔溃。   前段时间运营抱怨有张活动页出现了空白(第二种奔溃场景),导致用户无法访问,希望我们能主动监控到这种情况,而不是通过用户的上报。   后面和运维沟通,他那边目前只能监控接口的访问情况,无法监控静态资源,若要监控得自己想办法实现。   首先想到的自然是利用现有的监控系统来了解页面空白情况,例如某个项目5分钟内没有监控日志,那就认为出现了页面奔溃。   急匆匆的写了段定时任务,放到线上运行,发现这样监控会有一个很大漏洞。因为某些项目的访问量本来就不高,5分钟内没有日志是属于正常情况,所以只得作罢。 ## 一、页面奔溃   首先来解决第一种奔溃场景,在网上搜了些关键字,发现了些有用的资料,例如[如何监控网页崩溃](https://zhuanlan.zhihu.com/p/40273861),[前端崩溃监控优化历程](https://www.jackpu.com/web-qian-duan-crash-jian-kong-you-hua-li-cheng/)等。   这些资料提供了一个全新的思路来监控页面奔溃,基于Service Worker的崩溃统计方案。   简单地说就是一种心跳检测机制,在页面的脚本中创建Service Worker工作线程,然后定时地向该线程发送消息,即使网页奔溃了,线程还能存活。   在线程中接收消息并比对时间,当间隔时间大于15秒时,就认为超时没有心跳了,页面处于奔溃阶段,向监控系统上报相关信息。   在我操刀实现的时候,Service Worker没有运行成功,后面就改成了[Web Worker](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API)。   工作线程的代码保存在sw.js(如下所示),在参考一篇[Web Workers](https://www.html5rocks.com/zh/tutorials/workers/basics/%20)的文章时,他提到在线程中可以navigator对象,该对象正好有个sendBeacon()方法,可用于跨域请求。   但是没想到线程中用的[WorkerNavigator](https://developer.mozilla.org/en-US/docs/Web/API/WorkerNavigator),并没有该方法,后面无奈改成了[fetch()](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch)。   但是有跨域问题,要么在响应时加上跨域头,要么就无视直接发送,因为浏览器只会拦截响应不会拦截请求。 ~~~ var CHECK_CRASH_INTERVAL = 10 * 1000; // 每 10s 检查一次 var CRASH_THRESHOLD = 15 * 1000; // 15s 超过15s没有心跳则认为已经 crash var pages = {}, timer; function send(param) { fetch(param.src); }; function checkCrash() { var now = Date.now() for (var id in pages) { var page = pages[id]; if ((now - page.t) > CRASH_THRESHOLD) { // 上报 crash delete pages[id]; send(page.data); } } if (Object.keys(pages).length == 0) { clearInterval(timer) timer = null } } self.addEventListener('message', (e) => { var data = e.data; if (data.type === 'heartbeat') { // console.log('heartbeat'); pages[data.id] = { t: Date.now(), data: data.data } if (!timer) { timer = setInterval(function () { checkCrash() }, CHECK_CRASH_INTERVAL) } } else if (data.type === 'unload') { delete pages[data.id] } }) ~~~   在网页中加的代码如下,由于Worker加载的脚本有同源策略的限制,所以脚本和页面需要在相同的域名中。 ~~~ function monitorCrash(param) { var isCrash = param.isCrash; if (!isCrash || !window.Worker) return; var worker = new Worker("/sw.js"); var HEARTBEAT_INTERVAL = 5 * 1000; // 每五秒发一次心跳 var sessionId = getIdentity(); var heartbeat = function () { worker.postMessage({ type: "heartbeat", id: sessionId, data: { //在页面奔溃时,上报数据,需要将上报地址一起传递 src: param.src } }); }; window.addEventListener("beforeunload", function () { worker.postMessage({ type: "unload", id: sessionId }); }); var timer = setInterval(heartbeat, HEARTBEAT_INTERVAL); heartbeat(); } ~~~   上线后先在管理后台做测试,管理后台使用的是PC浏览器,马上就发现了比较严重的误报问题。   分析下来有可能是网页在标签栏不活动的时候,影响了定时器的执行,再次活动计算两个时间段的间隔,很有可能超出了15秒,而上报奔溃日志。   鉴于此,在没有完美解决方案之前,暂时将此功能下架。 ## 二、页面空白   再来解决第二种奔溃场景,现在开发都会依托React或Vue等库或框架,而这些都是用脚本来渲染出DOM结构的。   一旦在渲染时出现脚本错误(例如未定义的变量、浏览器不支持的语法等)就会中断渲染,从而就会出现页面无内容的情况。   这类监控并不需要使用Web Worker,只要我的监控SDK在业务脚本之前引入,就能保证监控代码正常运行。   监控原理就是加个定时器,查看渲染容器中是否是空白,若是空白就上报并关闭定时器,否则循环监控。   例如后台管理系统采用的是React,在HTML中会声明一个div元素,内容都会渲染到该元素中。 ~~~html
~~~   自定义一个判断条件,如下所示,在定时器中循环执行。 ~~~ document.getElementById("root").innerHTML.length > 0 ~~~   此处还有个小坑,就是定时器的运行时机,不能太早,太早判断的话,div元素中肯定没有内容,后面就将判断时机移到了[DOMContentLoaded](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/DOMContentLoaded_event)事件中。 ***** > 原文出处: [博客园-从零开始搞系列](https://www.cnblogs.com/strick/category/1928903.html) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
';