跳到主要内容

认识

2024年06月17日
柏拉文
越努力,越幸运

一、认识


Sentry

Sentry 是一个面向应用程序的实时错误与性能监控平台, 它主要帮助开发者快速捕获、分析和响应运行时错误与异常。Sentry 工作机制如下:

一、SDK 集成, 开发者需要在前端(如 JavaScript/TypeScript)或后端(如 PythonJava 等)项目中集成 Sentry 提供的 SDKSDK 会拦截程序中的未捕获异常、手动捕获的错误以及性能相关的事务数据。

二、数据采集

2.1 性能数据采集

  • FP (First Paint) 首次绘制任何像素: 表示从页面首次开始加载的时间点到页面开始首次绘制任何像素的时间点, 值越小越好。FPFCP 的区别?: FPFirst Paint, 只要有 任何像素 被绘制(例如背景色), 就会被记录为 FP; FCPFirst Contentful Paint, 记录的是第一个 DOM 实际内容(如文本、图片、SVG)的绘制,通常出现在 FP 之后。换句话说, 那么FP记录的时间就是开始绘制带背景色的body的时间点, 而FCP记录的则是 body 生成之后, 首次绘制来自DOM的有效内容的时间点, 这个时候FP的时间点就先于FCP

    • FP (First Paint) 首次绘制任何像计算思路: 1. 创建 PerformanceObserver 实例, 实例的回调函数中, 通过 entryList.getEntries() 获取所有记录的 paint 条目; 2. 过滤 FP 条目, 在 paint 类型的条目中,通常会包含 first-contentful-paintfirst-paint 两个记录, first-paint 表示浏览器首次绘制任何像素, 而 first-contentful-paint 才是真正有内容的绘制, 通过 entryList.getEntriesByName("first-paint") 来获取 first-paint 的所有条目。3. 浏览器在解析 HTMLCSS 之后,开始渲染第一个像素(即使只是背景色或空白元素)。这个时刻被记录为 FP, 所以我们取 first-paint 所有条目中的第一个。
    const fpObserver = new PerformanceObserver((entryList) => {
    const fpEntries = entryList.getEntriesByName("first-paint");
    const firstEntry = fpEntries[0];

    if(firstEntry){
    console.log("当前 FP 值:", firstEntry.startTime, "毫秒");
    }
    });

    fpObserver.observe({ type: "paint", buffered: true });
  • FCP (First Contentful Paint) 首次绘制任何 DOM 内容, 表示从页面首次开始加载的时间点到页面开始首次绘制任何DOM内容(例如文本、图像、SVGCanvas)的时间。FPFCP 的区别?: FPFirst Paint, 只要有 任何像素 被绘制(例如背景色), 就会被记录为 FP; FCPFirst Contentful Paint, 记录的是第一个 DOM 实际内容(如文本、图片、SVG)的绘制,通常出现在 FP 之后。换句话说, 那么FP记录的时间就是开始绘制带背景色的body的时间点, 而FCP记录的则是 body 生成之后, 首次绘制来自DOM的有效内容的时间点, 这个时候FP的时间点就先于FCP

    • FCP (First Contentful Paint) 首次内容渲染计算: 1. 创建 PerformanceObserver 实例, 实例的回调函数中, 通过 entryList.getEntries() 获取所有记录的 paint 条目; 2. 过滤 FCP 条目, 在 paint 类型的条目中,通常会包含 first-contentful-paintfirst-paint 两个记录, first-paint 表示浏览器首次绘制任何像素, 而 first-contentful-paint 才是真正有内容的绘制, 通过 entryList.getEntriesByName("first-contentful-paint") 来获取 first-contentful-paint 的所有条目。3. 浏览器在解析过程中,一旦检测到第一个非空内容(比如一个文本节点或图片)进入视口,就会记录下这个时间点,作为 FCP 的候选项, 所以我们取 first-contentful-paint 所有条目中的第一个。
    const fcpObserver = new PerformanceObserver((entryList) => {
    const fcpEntries = entryList.getEntriesByName("first-contentful-paint");
    const firstEntry = fcpEntries[0];

    if(firstEntry){
    console.log("当前 FCP 值:", firstEntry.startTime, "毫秒");
    }
    });

    fcpObserver.observe({ type: "paint", buffered: true });
  • LCP Largest Contentful Paint 最大内容绘制, 表示从页面首次开始加载的时间点到可视区域内最大内容完成渲染的时间。通常来说,为了提供良好的用户体验,我们应该努力将最大内容绘制控(LCP) 制在2.5秒或以内

    • LCP Largest Contentful Paint 最大内容绘制计算: 1. 创建 PerformanceObserver 实例, 实例回调函数中,通过 entryList.getEntries() 获取所有 LCP 条目,每个条目包含了候选元素的信息和渲染时间; 2. 获取最新候选记录, 由于 LCP 可能会随着页面加载不断更新,我们取最后一个候选记录作为当前 LCP 值,并输出 startTime(从 Navigation Start 开始计时,单位为毫秒); 3. 观察 LCP 条目 使用 observe 方法监听 largest-contentful-paint 条目类型, 并设置 buffered: true, 确保观察者能够接收到页面加载期间缓存的 LCP 数据;
    const lcpObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lastEntry = entries[entries.length - 1];
    console.log('当前 LCP 值:', lastEntry.startTime, '毫秒');
    });

    lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
  • FID (First Input Delay) 首次交互延迟, 表示是从用户首次与页面交互后到浏览器响应并处理该交互事件所需的时间。通常来说,我们可以认为,FID 时间在 100ms 内的能让用户得到良好的使用体验FID 越短, 说明页面对用户操作的响应越迅速, 从而提升用户体验。

    • FID (First Input Delay) 首次交互延迟计算: 1. 创建 PerformanceObserver 实例, 实例回调函数中,通过 entryList.getEntries() 获取所有 FID 条目, 记录包含了用户交互的起始时间(startTime)和浏览器开始处理该交互的时间(processingStart); 2. 在计算 FID 时, 我们关注的是用户的首次有效输入事件, 因此需要取 PerformanceObserver 收集到的第一个 first-input 类型的条目, 也就是列表中的第一个记录; 3. 观察 FID 条目 使用 observe 方法监听 first-input 条目类型, 并设置 buffered: true,确保观察者能够接收到页面加载期间缓存的 FID 数据;
    const fidObserver = new PerformanceObserver((enterList) => {
    const entries = enterList.getEntries();
    const firstEntry = entries[0];

    if (firstEntry) {
    const fid = firstEntry.processingStart - firstEntry.startTime;
    console.log("当前 FID 值", fid, "毫秒");
    }
    });

    fidObserver.observe({ type: "first-input", buffered: true });
  • CLS (Cumulative Layout Shift) 累计布局偏移, 表示页面加载期间由于布局变化而导致的不良用户体验,它统计了所有意外的布局偏移(layout shifts)的累计得分。通常来说,我们应该将 CLS 分数控制在 0.1 或以下

    • CLS (Cumulative Layout Shift) 累计布局偏移计算
    const clsObserver = new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();

    const clsValue = entries.reduce((_clsValue, entry) => {
    if (!entry.hadRecentInput) {
    _clsValue += entry.value;
    }
    return _clsValue;
    }, 0);

    console.log("当前 CLS 值", clsValue);
    });

    clsObserver.observe({ type: "layout-shift", buffered: true });
  • TTFB Time to First Byte 表示从客户端发送请求到接收到服务器返回的第一个字节所需的时间。TTFB 是衡量服务器响应速度和网络延迟的重要指标,对于评估整体页面加载性能和用户体验有着重要意义。

    • TTFB Time to First Byte 计算: 1. 获取导航条目, performance.getEntriesByType("navigation") 返回一个数组,其中包含了导航相关的性能记录。代码取数组中的第一个记录(通常只有一个)。2. TTFB 公式: TTFB=responseStartrequestStartTTFB=responseStart−requestStart
    const getNavigationEntry = () => {
    const navigationEntry =
    performance.getEntriesByType &&
    performance.getEntriesByType("navigation")[0];

    if (
    navigationEntry &&
    navigationEntry.responseStart > 0 &&
    navigationEntry.responseStart < performance.now()
    ) {
    return navigationEntry;
    }
    };

    const navigationEntry = getNavigationEntry();

    if (navigationEntry) {
    const ttfbValue = Math.max(
    navigationEntry.responseStart - navigationEntry.requestStart,
    0
    );

    console.log("当前 TTFB 值: ", ttfbValue);
    }

2.2 错误数据采集: 基于 Sentry 异常采集方案, 将一些方法进行重写: 1. 为了能自动捕获应用异常, Sentry 劫持覆写了 window.onerrorwindow.unhandledrejection 这两个 api; 2. Sentry 内部对异常发生的特殊上下文, 做了标记, 这些特殊上下文包括: dom 节点事件回调、setTimeout / setInterval 回调、xhr 接口调用、requestAnimationFrame 回调等

  • Promise 异常, 当 Promisereject 并且错误信息已经被 catch 处理的时候, 通过 rejectionhandled 事件方法进行捕获处理; 当 Promisereject 并且错误信息没有被 catch 处理的时候, 通过 unhandledrejection 事件方法进行捕获处理

  • 代码执行错误:

    • window.onerror 事件在冒泡阶段执行, 用于全局捕获错误。可以捕获异步错误, 捕获不到静态资源加载异常的错误, 原因是资源类型错误没有冒泡, 只能在捕获阶段捕获

    • window.addEventListener, 根据 useCapture 来决定在哪个阶段执行, 用于全局捕获错误。通过设置为捕获阶段执行, 可以捕获到静态资源加载异常的错误; 同样, 可以捕获异步错误。

三、数据上报

3.1 上报前处理

  1. 数据采样: 配置采样率, 按比例上报事件。例如,可以对低优先级的错误进行抽样上报,只记录部分事件,从而减少上传到 Sentry 的数据量。

  2. 数据过滤: 使用 Sentry SDK 提供的 beforeSend 钩子函数,对一些不必要或已知的非关键性错误进行过滤,不将它们上报到 Sentry,从而减轻数据负担。

  3. 削峰限流: 通过限流策略控制短时间内错误上报的数量, 避免瞬时大量错误事件冲垮系统。Sentry 的限流配置可以自动丢弃部分事件,以防止数据泛滥。

  4. 数据清理与保留策略: 定期清理历史数据,设定合理的数据保留周期,确保长时间内的数据不会积累到影响查询和分析的程度。

  5. 添加自定义标签和上下文: 在上报的错误数据中附加诸如版本号、环境(生产、测试等)、用户信息以及业务相关的上下文数据,能帮助后续定位问题并分析错误发生的原因。并对错误上报附加发布版本信息,便于追踪新版本可能引入的问题。

3.2 上报传输数据

  1. img, 选用 1px 的透明 GIF 作为图片地址, 上报埋点数据。1px GIF 的优势: img 没有跨域限制; img 标签加载并不需要挂载到页面上, 基于 jsnew image(), 设置其 src 之后就可以直接请求图片; 同样的响应, GIF 可以比 BMP 节约 41% 的流量, 比PNG 节约 35% 的流量, GIF才是最佳选择。但是 Image 是以 GET 方式请求图片资源的方式,将上报数据附在 URL 上携带到服务端,而 URL 地址的长度是有一定限制的。

  2. ajax, 一般而言,埋点域名并不是当前域名,因此请求会存在跨域风险,且如果 ajax 配置不正确可能会浏览器拦截。因此使用 ajax 这类请求并不是万全之策。

  3. navigator.sendBeacon(), 用于通过 HTTP POST 将少量数据异步传输到 Web 服务器。它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest)发送分析数据的一些问题。但是, 发送的请求不会在浏览器中的 Network 出现, 但是服务端确实可以接收到的。navigator.sendBeacon 会在合适的时机通过 HTTP POST 将少量数据异步发送到服务端, 不会阻塞当前页面的卸载, 也不会阻塞下个新页面的加载, 不存在性能问题, 这意味着 navigator.sendBeacon 在可靠、异步发送数据的同时, 不会影响当前页面、下一个页面。

四、数据分析

五、监控告警

参考资料


基于Sentry的前端性能监控平台搭建与应用

带你入门前端工程(七):前端监控

使用 Sentry 做异常监控 - Sentry 是如何做到自动捕获前端应用异常的呢 ?

Sentry原理--收集错误、上报

前端监控想用Sentry?看这一篇就够了🤩

前端错误监控方案 sentry

【得物技术】前端项目使用Sentry错误监控实

使用 Sentry 做异常监控 - 借助飞书捷径,我快速完成了 Sentry 上报异常的自动推送,点赞!