认识
一、认识
Sentry
是一个面向应用程序的实时错误与性能监控平台, 它主要帮助开发者快速捕获、分析和响应运行时错误与异常。Sentry
工作机制如下:
一、SDK
集成, 开发者需要在前端(如 JavaScript/TypeScript
)或后端(如 Python
、Java
等)项目中集成 Sentry
提供的 SDK
。SDK
会拦截程序中的未捕获异常、手动捕获的错误以及性能相关的事务数据。
二、数据采集
2.1 性能数据采集
-
FP
(First Paint
) 首次绘制任何像素: 表示从页面首次开始加载的时间点到页面开始首次绘制任何像素的时间点, 值越小越好。FP
与FCP
的区别?:FP
(First Paint
), 只要有 任何像素 被绘制(例如背景色), 就会被记录为FP
;FCP
(First 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-paint
和first-paint
两个记录,first-paint
表示浏览器首次绘制任何像素, 而first-contentful-paint
才是真正有内容的绘制, 通过entryList.getEntriesByName("first-paint")
来获取first-paint
的所有条目。3. 浏览器在解析HTML
和CSS
之后,开始渲染第一个像素(即使只是背景色或空白元素)。这个时刻被记录为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
内容(例如文本、图像、SVG
或Canvas
)的时间。FP
与FCP
的区别?:FP
(First Paint
), 只要有 任何像素 被绘制(例如背景色), 就会被记录为FP
;FCP
(First 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-paint
和first-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
公式:
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.onerror
和 window.unhandledrejection
这两个 api
; 2. Sentry
内部对异常发生的特殊上下文, 做了标记, 这些特殊上下文包括: dom
节点事件回调、setTimeout / setInterval
回调、xhr
接口调用、requestAnimationFrame
回调等
-
Promise
异常, 当Promise
被reject
并且错误信息已经被catch
处理的时候, 通过rejectionhandled
事件方法进行捕获处理; 当Promise
被reject
并且错误信息没有被catch
处理的时候, 通过unhandledrejection
事件方法进行捕获处理 -
代码执行错误:
-
window.onerror
事件在冒泡阶段执行, 用于全局捕获错误。可以捕获异步错误, 捕获不到静态资源加载异常的错误, 原因是资源类型错误没有冒泡, 只能在捕获阶段捕获 -
window.addEventListener
, 根据useCapture
来决定在哪个阶段执行, 用于全局捕获错误。通过设置为捕获阶段执行, 可以捕获到静态资源加载异常的错误; 同样, 可以捕获异步错误。
-
三、数据上报
3.1 上报前处理
-
数据采样: 配置采样率, 按比例上报事件。例如,可以对低优先级的错误进行抽样上报,只记录部分事件,从而减少上传到
Sentry
的数据量。 -
数据过滤: 使用
Sentry SDK
提供的beforeSend
钩子函数,对一些不必要或已知的非关键性错误进行过滤,不将它们上报到Sentry
,从而减轻数据负担。 -
削峰限流: 通过限流策略控制短时间内错误上报的数量, 避免瞬时大量错误事件冲垮系统。
Sentry
的限流配置可以自动丢弃部分事件,以防止数据泛滥。 -
数据清理与保留策略: 定期清理历史数据,设定合理的数据保留周期,确保长时间内的数据不会积累到影响查询和分析的程度。
-
添加自定义标签和上下文: 在上报的错误数据中附加诸如版本号、环境(生产、测试等)、用户信息以及业务相关的上下文数据,能帮助后续定位问题并分析错误发生的原因。并对错误上报附加发布版本信息,便于追踪新版本可能引入的问题。
3.2 上报传输数据
-
img
, 选用1px
的透明GIF
作为图片地址, 上报埋点数据。1px GIF
的优势:img
没有跨域限制;img
标签加载并不需要挂载到页面上, 基于js
去new image()
, 设置其src
之后就可以直接请求图片; 同样的响应,GIF
可以比BMP
节约41%
的流量, 比PNG
节约35%
的流量,GIF
才是最佳选择。但是Image
是以GET
方式请求图片资源的方式,将上报数据附在URL
上携带到服务端,而URL
地址的长度是有一定限制的。 -
ajax
, 一般而言,埋点域名并不是当前域名,因此请求会存在跨域风险,且如果ajax
配置不正确可能会浏览器拦截。因此使用ajax
这类请求并不是万全之策。 -
navigator.sendBeacon()
, 用于通过HTTP POST
将少量数据异步传输到Web
服务器。它主要用于将统计数据发送到Web
服务器,同时避免了用传统技术(如:XMLHttpRequest
)发送分析数据的一些问题。但是, 发送的请求不会在浏览器中的Network
出现, 但是服务端确实可以接收到的。navigator.sendBeacon
会在合适的时机通过HTTP
POST
将少量数据异步发送到服务端, 不会阻塞当前页面的卸载, 也不会阻塞下个新页面的加载, 不存在性能问题, 这意味着navigator.sendBeacon
在可靠、异步发送数据的同时, 不会影响当前页面、下一个页面。
四、数据分析
五、监控告警