技术创想98 | 浅谈前端性能及异常监控

文摘   科技   2023-12-07 17:59   北京  

引言

前端应用的性能和异常监控对于确保应用质量和用户体验至关重要。随着 Web 应用的复杂性和用户期望的增加,开发人员需要有效地监控应用的运行情况,并及时采取措施来提高应用的性能和稳定性。

我们团队的后端服务使用 SkyWalking 作为 APM 系统,它的前端 Agent —— SkyWalking Client JS 是一个非侵入式的扩展,支持将前端监控数据发送到 SkyWalking OAP Server,并使浏览器作为分布式追踪的起点。接下来就以 SkyWalking Client JS 为例,聊一聊前端异常及性能监控的建设方案。
性能监控

提升性能指标对于改善用户体验有很大意义。从页面开始加载到完整展示的背后会经历与服务器连接、页面加载、展示资源等很多环节,任意一个环节的卡顿都会影响网页的使用效果。我们可以借助 Performance API 获取性能指标数据。

比如想要获取页面加载相关数据,可以主动调用 performance.getEntriesByType('navigation'),返回的指标如下图所示,指标之间做减法即可算出每个阶段的耗时:

页面导航事件的时间线

再比如获取浏览器渲染相关的指标,如 First Contentful Paint、Largest Contentful Paint、First Input Delay 等。以 First Input Delay 为例,它是指从用户首次与网页互动到浏览器实际能够开始处理事件以响应该互动的时间。

页面加载性能指标示意

这里我们用被动监听的方式获取它:

new PerformanceObserver((entryList) => {  for (const entry of entryList.getEntries()) {    console.log(entry);  }}).observe({ type: "first-input", buffered: true });

打印出的结果如下,可以看到触发类型是 pointerdown 即点击鼠标,触发时间为页面加载完三秒多:

{    "name": "pointerdown",    "entryType": "first-input",    "startTime": 3566.5,    "duration": 64,    "processingStart": 3613.7000000029802,    "processingEnd": 3613.7999999970198,    "cancelable": true}

更省事的方法是用web-vitals库,优点是进行了封装和兼容处理:

import { onFID } from 'web-vitals';// Measure and log FID as soon as it's available.onFID(console.log);
SkyWalking Client JS 使用了第一种方法,计算出了 dnsTime、domReadyTime 等指标时长,在页面加载完毕后自动汇总上报给后端。

异常监控

2.1 异常类型

异常监控主要包含代码运行时错误、网络请求错误和资源加载错误的监控等。
  • TypeError、SyntaxError 等异常:可以通过 window.onerror window.addEventListener('error', callback) 捕获,回调函数中能获取到异常信息、URL、行列号和堆栈。也可以通过 try catch 手动捕获异常后收集。

     window.onerror = (a, b, c, d) => {   console.log(`message: ${a}`);  // message: Uncaught ReferenceError: a is not defined   console.log(`source: ${b}`);  // source: http://localhost:8000/test.html   console.log(`lineno: ${c}`);  // lineno: 24   console.log(`colno: ${d}`);  // colno: 1   return true; }; a.b = 123
    • Promise 异常:和普通异常不同,未被 catch Promise 异常需要通过 window.addEventListener('unhandledrejection', callback) 捕获。
      window.addEventListener("unhandledrejection", (evt) => {  console.log(`unhandledrejection: ${evt.reason}`);  // TEST});Promise.reject('TEST')
      • 资源加载异常:可以通过 window.addEventListener('error', callback, true) 捕获,在回调函数中需要判断 event target 类型,过滤出 script/link/image 等资源。值得注意的是资源加载引发的错误无法被 window.onerror 抓取,原因是它们没有冒泡,只能在捕获阶段获取到。

        <script>  window.addEventListener('error', (evt) => {    if (evt.target.tagName === 'IMG') {      console.log('Image load error: ', evt.target.src);      // Image load error:  http://localhost:8000/not-found-image.jpg    }  }, true)</script><img src="./not-found-image.jpg">
        • 请求异常:可以通过覆写 fetch XMLHttpRequest 实现,在响应里判断 HTTP 状态码或自定义的业务异常码后上报。
          const originalFetch = window.fetch;// 重写 fetch 函数window.fetch = function(url, options) {  return new Promise((resolve, reject) => {    // 调用原始的 fetch 函数发起请求    originalFetch(url, options)      .then(response => {        // 检查响应的状态码并上报...      })      .catch(error => {        // 捕获请求异常...      });  });};

          2.2 日志上报

          常见的日志上报方式有以下几种
          • Image 标签:在页面中添加一个图片,将日志内容拼接在 URL 里。本质是触发 GET 请求,简单易用。
          • XHR/fetch:灵活的异步请求,能发送大量数据。
          • navigator.sendBeacon()  :适用于需要在页面卸载时或导航发生时发送数据,它是异步非阻塞的,浏览器会尽量完成数据发送而不影响新页面载入。不过它不支持自定义 header 等信息,有需求可以用 fetch API 的 keepalive 属性(和 HTTP header 中的 Keep-Alive 不是一回事)实现同样的功能。
          • APP 代理:如果页面运行在原生 APP 中,通过 jsbridge 将日志委托给客户端上报也是一种常用手段。
          为了减少网络请求,日志通常会批量上报。SkyWalking 维护了一个队列,默认每一分钟通过 XHR 上报一次异常。同时通过监听 beforeunload 事件,在页面即将离开时通过 sendBeacon 将队列一次性上报完,来保证队列中不会有未上报的异常遗留:
          public fireTasks() {  if (!(this.queues && this.queues.length)) {    return;  }  new Report('ERRORS', this.collector).sendByXhr(this.queues);  this.queues = [];}
          public finallyFireTasks() { window.addEventListener('beforeunload', () => { if (!this.queues.length) { return; } new Report('ERRORS', this.collector).sendByBeacon(this.queues); });}
          public traceInfo() { // ... Task.addTask(this.logInfo, collector); Task.finallyFireTasks(); if (interval) { return; } interval = setInterval(() => { Task.fireTasks(); }, 60000);}

          分布式追踪

          在复杂的分布式系统中,一个请求可能会经过多个不同的服务和组件进行处理,涉及多个网络调用和跨越不同的进程或服务之间的通信。分布式追踪的目标是跟踪整个请求的路径,并收集关键的性能指标,以便全面了解请求在系统中的执行情况。

          简单来说,SkyWalking 通过维护 Trace Context 实现链路追踪和分析。它制定了 Cross Process Propagation Headers Protocol v3(sw8)协议用于上下文传播,这个协议是由 - 分割的 8 个字段组成的,具体含义可参考官方文档

          SkyWalking Client JS 覆写了 fetch XHR,发出请求时会根据协议规范生成数据并在 header 中加入 sw8 字段。SkyWalking 检测到符合协议的数据后,就会解析它并作为上下文传递到后续服务中,由此实现了以浏览器请求为起点的链路追踪。

          const traceIdStr = String(encode(traceId));    // uuid 生成的随机值const segmentId = String(encode(traceSegmentId));    // uuid 生成的随机值const service = String(encode(segment.service));    // 应用名称const instance = String(encode(segment.serviceInstance));    // 应用版本const endpoint = String(encode(customConfig.pagePath));    // 页面路径const peer = String(encode(url.host));    // location.hostconst index = segment.spans.length;const values = `${1}-${traceIdStr}-${segmentId}-${index}-${service}-${instance}-${endpoint}-${peer}`;headers['sw8'] = values;

          前端请求成功后,可以在 SkyWalking 管理后台查看追踪数据,包含 PV、异常数量和比例、每个页面(endpoint)的性能数据等:

          SkyWalking 后台展示的异常指标数据

          SkyWalking 后台展示的性能指标数据

          还可以追踪具体请求,可以看到前端请求和后端已经打通了,根据调用链能够清晰地定位到后端异常的发生位置,点击详情能够直接查看日志:

          特定请求对应的链路

          总结

          以上就是以 SkyWalking Client JS 为例,对前端异常及性能监控的简单介绍。通过合适的监控方法和工具,我们可以及时发现并解决潜在的问题,保障应用的性能和稳定性,从而为用户提供更好的使用体验。

          参考资料

          • https://skywalking.apache.org/docs/main/next/en/api/x-process-propagation-headers-v3/

          • https://skywalking.apache.org/blog/end-user-tracing-in-a-skywalking-observed-browser/

          • https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon

          • https://developer.mozilla.org/en-US/docs/Web/API/Performance_API

          • https://web.dev/articles/vitals?hl=zh-cn


          关于领创集团

          (Advance Intelligence Group)
          领创集团成立于 2016年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的先享后付平台 Atome 和数字金融服务。2021年 9月,领创集团宣布完成超4亿美元 D 轮融资,融资完成后领创集团估值已超 20亿美元,成为新加坡最大的独立科技创业公司之一。




          领创集团Advance Group
          领创集团是亚太地区AI技术驱动的科技集团。
           最新文章