[前端监控SDK| 青训营笔记]

122 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第11天

怎么设计一个监控

先设计出“数据采集、日志上报、日志查询”这三大块。

  • 数据采集:负责在客户端采集需要监控的数据。这里的客户端可以指浏览器、APP、小程序,因此对每种容器都可以实现一个 SDK 来采集监控数据。本文只针对浏览器来描述设计方案。
  • 日志上报:将采集到的数据通过 HTTP 请求传给服务端,数据以日志的形式存储到数据库。
  • 日志查询:支持对日志按条件查询,并提供对应的 UI 界面功能。

幸运的是,对于日志的存储和查询,市面上已经有成熟的开源技术方案 ELK,它是三个开源项目的首字母缩写。

  • ElasticSearch 是一个分布式搜索引擎,提供搜集、分析、存储数据三大功能。
  • Logstash 是服务器端数据处理的管道,能够同时从多个来源采集、转换数据,然后将数据发送到诸如 ElasticSearch 等“存储库”中。
  • Kibana 则可以为 Logstash 和 ElasticSearch 提供友好的 Web 界面,可以帮助使用者汇总、分析和搜索重要数据日志。

我们用到了 ELK 中的“E”和“K”,而日志数据的处理逻辑放在了 HTTP Server 中进行,处理完后直接将日志数据落到 ElasticSearch 中。

资源加载监控

对于前端资源加载信息,我们想知道页面里加载了哪些资源,它们的 url 是什么,它们是否加载成功,以及它们加载了多久。页面中加载的所有资源,都可以在 performance.getEntries() 里拿到。同样在实现之前,我们先查下 MDN 定义的 PerformanceEntry。

在具体实现时,可以使用 performance.getEntriesByType('resource') 获取到所有资源加载的信息,包括静态资源和动态资源,因此需要结合 initiatorType 进一步过滤。

export function watchResource(config: SdkConfig) {
  function reportAssets () {
    if (isFunction(performance.getEntriesByType)) {
      const entries = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
      
      // 过滤掉非静态资源的 performance entry
      const resourceEntries = arrayFilter(entries, function(entry) {
        return ['fetch', 'xmlhttprequest', 'beacon'].indexOf(entry.initiatorType) === -1;
      });

      // 上报日志
      if (resourceEntries.length) {
        sender.report('resource', arrayMap(resourceEntries, function(entry) {
          return {
            url: entry.name,
            httpCode: 200,
            time: Math.round(entry.duration),
          };
        }));
      }

      // 清空这一轮所取到的 performance entry
      if (isFunction(performance.clearResourceTimings)) {
        performance.clearResourceTimings();
      }

      // 可定时2秒收集一次
      setTimeout(reportAssets, 2000);
    }
  }

  if (document.readyState === 'complete') {
    reportAssets();
  } else {
    addEventListener(window, 'load', reportAssets);
  }
};

一个真实的业务页面中,资源加载一定是逐步进行的,有些资源本身就做了延迟加载,有些是需要用户发生一些交互后才会去请求一些资源。所以我们想一次性去收集到所有的资源加载信息,这是不可能的,我们只能够定时反复地去收集。整体流程上,当页面 DOM ready 或 onload 时,就可以触发收集一轮资源加载的日志。

在这一轮收集结束后,记得一定要通过调用 clearResourceTimings 将 performance entries 里的信息清空,避免在下一轮收集时取到重复的资源。

这里有个小疑问,通过 performance.getEntriesByType('resource') 获取到的 entry 对象里没有 HTTP 状态码,那要怎么区分出资源加载的成功与否呢?这时就要用到组合拳了,回想一下,当页面中有一个资源加载 404 时,浏览器 console 里是不是会有个报错呢?

如图这种报错,就可以在 window.onerror 里监听到,于是解决的思路就有了。可以从报错信息里提取出加载失败的资源 url,或者从报错的 event.target 里去找元素的 src 属性来作为资源 url。找到资源 url 后,可以用 performance.getEntriesByName 把它对应的 entry 对象找出来,就可以上报日志了。

页面性能监控

页面性能监控的实现比较简单,整体上也是在页面 DOM ready 或 onload 时,去收集 performance 数据。只需要上报一次,具体上报的性能数据可以参考 PerformanceTiming 里定义的属性。