前端监控 SDK | 青训营笔记

66 阅读2分钟

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

课程重点

  1. 为什么要聊前端监控
  2. 前端监控之常用性能指标
  3. 前端监控之前端常见异常
  4. 封装一个通用的SDK

为什么要聊前端监控

通过对页面数据的采集上报,来帮助开发者更快速地对质量差的页面进行分析和归因。 性能不佳的页面会导致糟糕的用户体验,从而引起流量减少、转化率降低等业务负面影响。

监控方向

前端监控主要包括三个方向

  • 性能指标
  • 异常事件
  • 用户行为

前端监控之常用性能指标

以用户为中心的性能指标:不拘泥于技术细节,而是从用户能理解能体验到的方面作为性能监控的指标。

加载流:

  1. FP(First Paint)
  2. FCP(First Contentful Paint)
  3. FMP(First Meaningful Paint)非标准化。
  4. TTI(Time to Interactive) 越小代表用户可以更早操作页面,用户体验也就越好。

LCP: 最大的内容在可视区域内变得可见的时间点。对用户来说更容易理解,更容易计算和上报。

TBT: 量化主线程在空闲前的繁忙程度,因为主线程繁忙时UI线程会被阻塞。

前端监控之前端常见异常

静态资源错误

Failed to load resource: the server responded with a status of 404 大多数时候是网络异常捏,404 not found

请求异常

404、403、500、502

特殊情况: status=0 请求无法执行

JS错误

有各种各样的原因,不少情况都来自于边缘状况的判断不足,使用TS能减少出现的次数。

白屏异常

通常通过判断DOM树结构来粗略判断白屏是否产生。

产生原因:

  1. JS错误导致关键资源渲染失败

  2. 资源加载失败

  3. JS线程过于繁忙

封装一个通用的SDK

按需加载监控能力

function createSdk(url: string) {
  const monitors: Array<{ name: string, start: Function }> = [];
  const sdk = {
    url,
    report,
    loadMonitor,
    monitors,
    start,
  }
  function report({ name: string, data: any }) {
    // 注意:数据发送前需要先序列化为字符串
    navigator.sendBeacon(url, JSON.stringify({ name: string, data: any }));
  }
  function loadMonitor({ name: string, start: Function }) {
    monitors.push({ name: string, start: Function });
    // 实现链式调用
    return sdk;
  }
  function start() {
    monitors.forEach(m => m.start());
  }
  return sdk;
}
const sdk = createSdk("111.com");
const jsMonitor = createJsErrorMonitor(sdk.report);
sdk.loadMonitor(jsMonitor).loadMonitor(createPerfMonitor(sdk.report));
sdk.start();
throw (new Error('test'));



function createJsErrorMonitor(report: ({ name: string, data: any }) => void) {
  const name = "js-error";
  function start() {
    window.addEventListener("error", (e) => {
      // 只有 error 属性不为空的 ErrorEvent 才是一个合法的 js 错误
      if (e.error) {
        report({ name, data: { type: e.type, message: e.message } });
      }
    });
    window.addEventListener("unhandledrejection", (e) => {
      report({ name, data: { type: e.type, reason: e.reason } });
    });
  }
  return { name, start }
}

function createPerfMonitor(report: ({ name: string, data: any }) => void) {
  const name = 'performance';
  const entryTypes = ['paint', 'largest-contentful-paint', 'first-input']
  function start() {
    const p = new PerformanceObserver(list => {
      for (const entry of list.getEntries()) {
        report({ name, data: entry });
      }
    })
    p.observe({ entryTypes });
  }
  return { name, start }
}