美文网首页
如何监控网页崩溃?

如何监控网页崩溃?

作者: 小豆soybean | 来源:发表于2022-07-26 16:08 被阅读0次

    转:https://zhuanlan.zhihu.com/p/40273861
    利用serviceWorker.

    本文是如何监控网页的卡顿?的下篇。今天我们把话题聚焦在如何监控网页的崩溃上。

    崩溃和卡顿有何差别?

    卡顿也就是网页暂时响应比较慢,JS 可能无法及时执行,这也是上篇网页卡顿监控所依赖的技术点。

    但崩溃就不一样了,网页都崩溃了,页面看不见了,JS 都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢?

    但,天无绝人之路,方法总是有的。

    load 与 beforeunload 事件

    搜遍互联网,几乎找不到方法,最终碰上了这篇文章。本文利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。

    http://jasonjl.me/blog/2015/06/21/taking-action-on-browser-crashes/jasonjl.me/blog/2015/06/21/taking-action-on-browser-crashes/

      window.addEventListener('load', function () {
          sessionStorage.setItem('good_exit', 'pending');
          setInterval(function () {
             sessionStorage.setItem('time_before_crash', new Date().toString());
          }, 1000);
       });
    
       window.addEventListener('beforeunload', function () {
          sessionStorage.setItem('good_exit', 'true');
       });
    
       if(sessionStorage.getItem('good_exit') &&
          sessionStorage.getItem('good_exit') !== 'true') {
          /*
             insert crash logging code here
         */
          alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
       }
    
    

    一图胜千言:

    image.png

    <figcaption style="margin-top: calc(0.666667em); padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">使用 load 和 beforeunload 事件实现崩溃监控</figcaption>

    这个方案巧妙的利用了页面崩溃无法触发 beforeunload 事件来实现的。

    在页面加载时(load 事件)在 sessionStorage 记录 good_exit 状态为 pending,如果用户正常退出(beforeunload 事件)状态改为 true,如果 crash 了,状态依然为 pending,在用户第2次访问网页的时候(第2个load事件),查看 good_exit 的状态,如果仍然是 pending 就是可以断定上次访问网页崩溃了!

    但这个方案有问题:

    1. 采用 sessionStorage 存储状态,但通常网页崩溃/卡死后,用户会强制关闭网页或者索性重新打开浏览器,sessionStorage 存储但状态将不复存在;
    2. 如果将状态存储在 localStorage 甚至 Cookie 中,如果用户先后打开多个网页,但不关闭,good_exit 存储的一直都是 pending,完了,每有一次网页打开,就会有一个 crash 上报。

    全民直播 一开始采用的就是这个方案,发现就算页面做了优化,crash 不下降,与 PV 保持比例,才意识到这个方案的问题之处。

    基于 Service Worker 的崩溃统计方案

    随着 PWA 概念的流行,大家对 Service Worker 也逐渐熟悉起来。基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:

    1. Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
    2. Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
    3. 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。

    基于以上几点,我们可以实现一种基于心跳检测的监控方案:

    [图片上传失败...(image-73fddc-1658822703499)]

    • p1:网页加载后,通过 postMessage API 每 5s 给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
    • p2:网页在 beforeunload 时,通过 postMessage API 告知自己已经正常关闭,sw 将登记的网页清除;
    • p3:如果网页在运行的过程中 crash 了,sw 中的 running 状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
    • sw:Service Worker 每 10s 查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。

    一些简化后的检测代码,给大家作为参考:

    // 页面 JavaScript 代码
    if (navigator.serviceWorker.controller !== null) {
      let HEARTBEAT_INTERVAL = 5 * 1000; // 每五秒发一次心跳
      let sessionId = uuid();
      let heartbeat = function () {
        navigator.serviceWorker.controller.postMessage({
          type: 'heartbeat',
          id: sessionId,
          data: {} // 附加信息,如果页面 crash,上报的附加数据
        });
      }
      window.addEventListener("beforeunload", function() {
        navigator.serviceWorker.controller.postMessage({
          type: 'unload',
          id: sessionId
        });
      });
      setInterval(heartbeat, HEARTBEAT_INTERVAL);
      heartbeat();
    }
    
    
    • **sessionId **本次页面会话的唯一 id;
    • postMessage 附带一些信息,用于上报 crash 需要的数据,比如当前页面的地址等等。
    const CHECK_CRASH_INTERVAL = 10 * 1000; // 每 10s 检查一次
    const CRASH_THRESHOLD = 15 * 1000; // 15s 超过15s没有心跳则认为已经 crash
    const pages = {}
    let timer
    function checkCrash() {
      const now = Date.now()
      for (var id in pages) {
        let page = pages[id]
        if ((now - page.t) > CRASH_THRESHOLD) {
          // 上报 crash
          delete pages[id]
        }
      }
      if (Object.keys(pages).length == 0) {
        clearInterval(timer)
        timer = null
      }
    }
    
    worker.addEventListener('message', (e) => {
      const data = e.data;
      if (data.type === 'heartbeat') {
        pages[data.id] = {
          t: Date.now()
        }
        if (!timer) {
          timer = setInterval(function () {
            checkCrash()
          }, CHECK_CRASH_INTERVAL)
        }
      } else if (data.type === 'unload') {
        delete pages[data.id]
      }
    })
    
    

    都挺简单的代码,不细说了。

    方案的可行性

    兼容性:

    Service Worker 的普及率已经相当高了,鉴于国内各种浏览器都是 Chrome 内核,而且版本已经在 Chrome 45 以上,已经覆盖了相当一部分用户。作为监控,数据覆盖大部分就好。

    image.png

    可靠性:

    这应该是我目前已知可以相对准确判断出网页崩溃的方式了。不过我们的方案还在测试环境,上线一段时间后再给大家共享数据。

    对浏览器厂商的建议

    题图的 Crash 列表,可以在 Chrome 中访问 chrome://crashes/ 看到,如果厂商可以提供一个 API,在页面打开时,可以获知用户上一次崩溃的信息就很棒了!

    相关文章

      网友评论

          本文标题:如何监控网页崩溃?

          本文链接:https://www.haomeiwen.com/subject/ltboirtx.html