loading

错误监控,性能监控

# 错误监控

前端捕获异常分为全局捕获和单点捕获。

  • 全局捕获代码集中,易于管理;
  • 单点捕获作为补充,对某些特殊情况进行捕获,但分散,不利于管理。

# 全局捕获

  • 通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有: window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等

框架级别的全局监听,

  • 例如aixos中使用interceptor进行拦截, vue、react都有自己的错误采集接口
  • 通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常
  • 对实例方法重写(Patch),在原有功能基础上包裹一层,例如对setTimeout进行重写,在使用方法不变的情况下也可以异常捕获

# 单点捕获

在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:

  • try…catch
  • 专门写一个函数来收集异常信息,在异常发生时,调用该函数
  • 专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行结果和原函数一模一样,只是在发生异常时可以捕获异常

# window.onerror

监控JavaScript运行时错误(包括语法错误)和 资源加载错误

window.onerror = function(message, source, lineno, colno, error) { ... }
window.addEventListener('error', function(event) { ... }, true)
// 函数参数:
    // message:错误信息(字符串)。可用于HTML onerror=""处理程序中的event。
    // source:发生错误的脚本URL(字符串)
    // lineno:发生错误的行号(数字)
    // colno:发生错误的列号(数字)
    // error:Error对象(对象

可以看到 JS 错误监控里面有个 window.onEerror,
又用了 window.addEventLisenter('error'),
其实两者并不能互相代替。
window.onError 是一个标准的错误捕获接口,它可以拿到对应的这种 JS 错误;
window.addEventLisenter('error')也可以捕获到错误,
但是它拿到的 JS 报错堆栈往往是不完整的。
同时 window.onError 无法获取到资源加载失败的一个情况,
必须使用 window.addEventLisenter('error')来捕获资源加载失败的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# unhandledrejection

捕获未被catch的reject状态的promise

window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
1
2
3

# setTimeout、setInterval、requestAnimationFrame等

通过代理的方式把原来的方法拦截一下在调用真实的方法之前做一些自己的事情

const prevSetTimeout = window.setTimeout;

window.setTimeout = function(callback, timeout) {
  const self = this;
  return prevSetTimeout(function() {
    try {
      callback.call(this);
    } catch (e) {
      // 捕获到详细的错误,在这里处理日志上报等了逻辑
      // ...
      throw e;
    }
  }, timeout);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Vue的Vue.config.errorHandler , React的ErrorBoundary

# 性能监控

在说明各项指标之前,先说一下, web-vitals 库,这个库可以用来计算各指标

npm install web-vitals

import { getLCP, getFID, getCLS } from 'web-vitals'
getCLS((metric) => console.log('cls: ' + metric.value))
getFID((metric) => console.log('fid: ' + metric.value))
getLCP((metric) => console.log('lcp: ' + metric.value))

1
2
3
4
5
6
7

# 前端性能监控API——Performance

# 常用属性

  • memory:主要是和内存相关,显示此刻的内存占用情况,图中可以发现其有三个属性

jsHeapSizeLimit:上下文内可用堆的最大体积 totalJSHeapSize:当前js堆栈总内存大小 usedJSHeapSize:当前被使用的内存大小,不能大于totalJSHeapSize,大于就可能有内存泄漏。

  • navigation:表示出现在当前浏览上下文的 navigation 类型,图中可以发现其有两个属性

redirectCount:重定向的次数,表示当前页重定向了几次 type:表示页面打开类型,可选值有 0、1、2、255

0:通过常规的导航访问页面,例如点击链接 1:通过刷新(包括用js调用的刷新)访问页面 2:通过前进或者后退按钮访问页面 255:除了以上的方式访问页面

  • timing:统计了从浏览器从网址开始导航到 window.onload事件触发的一系列关键的时间点
  • navigationStart:表示在同一浏览上下文中上一个文档终止时的时间戳。如果没有以前的文档,这个值将与fetchStart相同
  • unloadEventStart:表示窗口中的前一个网页(与当前页面同域)unload的时间戳。如果没有前一个网页,或者前一个网页和当前页面不是同域,则返回值为0。
  • unloadEventEnd:表示当unload事件结束时的时间戳。 果没有前一个网页,或者前一个网页和当前页面不是同域,则返回值为0。
  • redirectStart:表示当第一个HTTP重定向开始时的时间戳。如果没有重定向,或者其中一个重定向不是同域,则返回值为0。
  • redirectEnd:表示当最后一个HTTP重定向完成时,即接收到HTTP响应的最后一个字节时的时间戳。如果没有重定向,或者其中一个重定向不是同域,则返回值为0。
  • fetchStart:表示当浏览器准备好使用HTTP请求获取文档时的时间戳。这个时刻是发生在检查任何应用程序缓存之前。
  • domainLookupStart:表示当DNS域名查询开始时的时间戳。如果使用了持久连接,或者信息存储在缓存或本地资源中(即无DNS查询),则该值将与fetchStart相同。
  • domainLookupEnd:表示当DNS域名查询完成时的时间戳。如果使用了持久连接,或者信息存储在缓存或本地资源中(即无DNS查询),则该值将与fetchStart相同。
  • connectStart:表示HTTP TCP开始建立连接的时间戳。如果传输层报告了一个错误,并且重新开始建立连接,则给出最后一次建立连接的开始时间戳。如果使用持久连接,则该值与fetchStart相同。
  • connectEnd:表示HTTP TCP完成建立连接(完成握手)的时间戳。如果传输层报告了一个错误,并且重新开始建立连接,则给出最后建立连接的结束时间。如果使用持久连接,则该值与fetchStart相同。当所有安全连接握手或SOCKS身份验证都被终止时,该连接被视为已打开。
  • secureConnectionStart:表示当安全连接握手(HTTPS连接)开始时的时间戳。如果没有安全连接,则返回0。
  • requestStart:表示浏览器发送请求从服务器或本地缓存中获取实际文档的时间戳。如果传输层在请求开始后失败,并且连接重新打开,则此属性将被设置为与新请求对应的时间。
  • responseStart:表示当浏览器从服务器的缓存或本地资源接收到响应的第一个字节时的时间戳。
  • responseEnd:表示当浏览器从服务器、缓存或本地资源接收到响应的最后一个字节时或者当连接被关闭时(如果这是首先发生的)的时间戳。
  • domLoading:表示当解析器开始工作,也就是开始渲染dom树的时间戳。这时document.readyState变为'loading',相应的readystatechange事件被抛出。
  • domInteractive:表示解析器完成解析dom树的时间戳,这时document.readyState变为'interactive',相应的readystatechange事件被抛出。这时候只是解析完成DOM树,还没开始加载网页内的资源。
  • domContentLoadedEventStart:表示DOM解析完成后,网页内的资源开始加载的时间戳。就在解析器发送DOMContentLoaded事件之前。
  • domContentLoadedEventEnd:表示DOM解析完成后,网页内的资源加载完成的时间戳。即在所有需要尽快执行的脚本(按顺序或不按顺序)被执行之后。
  • domComplete:表示当解析器完成它在主文档上的工作时,也就是DOM解析完成,且资源也准备就绪的时间。document.readyState变为'complete',相应的readystatechange事件被抛出。
  • loadEventStart:表示当为当前文档发送load事件时,也就是load回调函数开始执行的时间。如果这个事件还没有被发送,它将返回0。
  • loadEventEnd:表示当load事件的回调函数执行完毕的时间,即加载事件完成时。如果这个事件还没有被发送,或者还没有完成,它将返回0。

借助performance.timing里面的各个时间戳,我们可以获取到

  • DNS解析耗时 : performance.timing.domainLookupEnd - performance.timing.domainLookupStart
  • TCP连接耗时 : performance.timing.connectEnd - performance.timing.connectStart
  • SSL连接耗时 : performance.timing.connectEnd - performance.timing.secureConnectionStart
  • request耗时 : performance.timing.responseEnd - performance.timing.responseStart
  • 解析DOM树耗时 : performance.timing.domComplete - performance.timing.domInteractive
  • domready时间 : performance.timing.domContentLoadedEventEnd - performance.timing.fetchStart
  • onload时间 : performance.timing.loadEventEnd - performance.timing.fetchStart

# 各项指标

以下指标如果手动计算的话需要在 window.onload 事件中计算,在此之前计算的话因为所有的数据都有可能不断变化

  • FCP:首屏时间,首次内容绘制的时间,指页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。
console.log(
    "FCP:" +
      performance.getEntriesByName("first-contentful-paint")[0].startTime
);

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntriesByName('first-contentful-paint')) {
    console.log('FCP candidate:', entry.startTime, entry)
  }
}).observe({ type: 'paint', buffered: true })

import {getFCP} from 'web-vitals';

// 当 FCP 可用时立即进行测量和记录。
getFCP(console.log);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • FP:白屏时间,首次渲染的时间点。FP和FCP有点像,但FP一定先于FCP发生,例如一个页面加载时,第一个DOM还没绘制完成,但是可能这时页面的背景颜色已经出来了,这时FP指标就被记录下来了。而FCP会在页面绘制完第一个 DOM 内容后记录。
console.log(
    "FP:" + performance.getEntriesByName("first-paint")[0].startTime
  );

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

1
2
3
4
5
6
7
8
9
10
  • LCP:根据页面开始加载的时间报告可视区域内可见的最大图像或文本块完成渲染的计算时间,用于测验加载性能,衡量网站初次载入速度。 应该控制该值在2.5 秒以内;最大指元素的尺寸大小,这个大小不包括可视区域之外或者是被裁剪的不可见的溢出。也不包括元素的Margin / Padding / Border等。

利用 PerformanceObserver 构造函数创建一个性能检测对象,最新的largest-contentful-paint条目的startTime就是LCP值

let observer = new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
});
observer.observe({ type: "largest-contentful-paint", buffered: true });

1
2
3
4
5
6
7
  • FID:首次输入延迟时间,主要为了测量页面加载期间响应度,测量交互性。为了提供良好的用户体验,页面的 FID 应为100 毫秒或更短。测量用户第一次与页面交互(单击链接、点按按钮等等)到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。

创建PerformanceObserver对象监听 first-input 类型的条目,并获取条目的startTime和processingStart时间戳的差值作为结果


new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    const delay = entry.processingStart - entry.startTime;
    console.log('FID candidate:', delay, entry);
  }
}).observe({type: 'first-input', buffered: true});

1
2
3
4
5
6
7
8
  • CLS:累积布局偏移,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1. 或更少。CLS是测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数。每当一个可见元素从一个已渲染帧变更到另一个已渲染帧时,就是发生了布局偏移。所谓一连串布局偏移,是指一个或者多个的布局偏移,这些偏移相隔少于1秒,总持续时间最大为5秒。而最大一连串就是所有的一连串布局偏移中偏移累计分数最大的一连串。

测量CLS的原理是,创建一个PerformanceObserver对象来侦听意外偏移layout-shift条目

let clsValue = 0;
let clsEntries = [];

let sessionValue = 0;
let sessionEntries = [];

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    // 只将不带有最近用户输入标志的布局偏移计算在内。
    if (!entry.hadRecentInput) {
      const firstSessionEntry = sessionEntries[0];
      const lastSessionEntry = sessionEntries[sessionEntries.length - 1];

      // 如果条目与上一条目的相隔时间小于 1 秒且
      // 与会话中第一个条目的相隔时间小于 5 秒,那么将条目
      // 包含在当前会话中。否则,开始一个新会话。
      if (sessionValue &&
          entry.startTime - lastSessionEntry.startTime < 1000 &&
          entry.startTime - firstSessionEntry.startTime < 5000) {
        sessionValue += entry.value;
        sessionEntries.push(entry);
      } else {
        sessionValue = entry.value;
        sessionEntries = [entry];
      }

      // 如果当前会话值大于当前 CLS 值,
      // 那么更新 CLS 及其相关条目。
      if (sessionValue > clsValue) {
        clsValue = sessionValue;
        clsEntries = sessionEntries;

        // 将更新值(及其条目)记录在控制台中。
        console.log('CLS:', clsValue, clsEntries)
      }
    }
  }
}).observe({type: 'layout-shift', buffered: true});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  • TTFB:首包时间,资源请求到获取第一个字节之间的时间,包括以下阶段的总和

重定向时间 Service Worker 启动时间(如果适用) DNS 查询 连接和 TLS 协商 请求,直到响应的第一个字节到达

console.log(
  'TTFB:' +
    (performance.timing.responseStart - performance.timing.navigationStart)
)

new PerformanceObserver((entryList) => {
  const [pageNav] = entryList.getEntriesByType('navigation')
  console.log(`TTFB: ${pageNav.responseStart}`)
}).observe({
  type: 'navigation',
  buffered: true,
})

import {getTTFB} from 'web-vitals';

// 当 TTFB 可用时立即进行测量和记录。
getTTFB(console.log);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • SI:速度指数衡量页面加载期间内容的视觉显示速度,也就是页面填充快慢的指标。

良好的SI应该控制在3.4以内。

  • TTI:可交互时间,指标测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。

良好的TTI应该控制在5秒以内。

  • TBT:总阻塞时间,也就是从FCP到TTI之间的时间。

# 性能测试工具

# Lighthouse

  • Lighthouse是谷歌官方开发的性能分析工具,目前已经嵌入到 chrome 开发者工具的选项卡中,不需要额外安装,可以直接使用。
  • 报告会包含 FCP、TTI、SI、TBT、LCP、CLS 六个指标数据,但是无法测试FID。

还有总的性能评分,以及SEO的分数和一些其他的优化建议等等,总的来说报告数据算是很齐全的。

# PageSpeed Insights 性能测试网站

https://pagespeed.web.dev/

输入网址就可以测试性能的网站,基本该有的指标数据都有,包括Lighthouse暂不支持的FID。

# WebPageTest 性能测试网站

https://www.webpagetest.org/

同样是一个输入网址就可以测试性能的网站。

可以选择全球各地进行性能测试,同样提供详细的检查结果报告,包括清晰的瀑布图数据,以及相关的优化建议。

有个问题就是这个网站测试要排队,往往要等一会才出结果。

最近更新时间: 2022/09/28 16:26:36
最近更新
01
2023/07/03 00:00:00
02
2023/04/22 00:00:00
03
2023/02/16 00:00:00