会话重放采集原理与数据完整性¶
会话重放(Session Replay)用于还原用户在 Web 页面中的操作过程。它不是屏幕录像:浏览器 SDK 记录页面完整快照和后续变化,将数据压缩、分段上传;播放时,平台再根据这些数据重建页面。
理解这一点有助于区分三种现象:
- 没有采集:未开始录制、未命中采样或浏览器能力不满足;
- 采集链路发生缺口:页面变化超出保护预算、网络永久失败或页面突然关闭;
- 数据已上传但视觉不完整:图片、字体或样式在播放时不可访问,或者内容受隐私规则和能力边界限制。
数据如何生成¶
初始化并开始录制
↓
会话采样与录制资格判断
↓
完整快照(页面基线)
↓
DOM、输入、滚动、鼠标、样式、Canvas 等增量
↓
压缩并生成有序分段
↓
常规网络发送;离页时尝试 beacon / XHR
↓
平台处理并生成可播放文件
1. 初始化和采样¶
SDK init() 完成后,还必须显式调用:
调用前发生的页面状态和操作不会被倒推补录。录制是否真正开始,还取决于当前 Session 是否命中 sessionReplaySampleRate 或 sessionReplayOnErrorSampleRate。
使用:
可以确认当前页面是否已经进入录制状态。
startSessionReplayRecording({ force: true }) 可强制当前 Session 具备回放资格。该选项会改变采样行为,建议仅在已明确需要强制录制的业务场景中使用。
2. 完整快照¶
录制开始时,SDK 会先生成一份可播放的页面基线,包括:
- 当前页面地址和视口;
- 焦点状态;
- DOM 结构、属性、文本和滚动位置;
- 浏览器支持时的 Visual Viewport 信息。
完整快照生成期间,如果页面 DOM 仍在快速变化,SDK 会放弃混合了不同时间状态的快照并重新构建。超大页面或持续高频变化的页面可能多次失败;连续恢复失败时,SDK 会停止当前页面的录制,避免继续生成无法正确播放的数据。
3. 增量记录¶
完整快照之后,SDK 持续记录:
- DOM 节点新增、删除、文本和属性变化;
- 鼠标、触摸、点击和滚动;
- 输入框值变化;
- 视口尺寸变化;
- 音视频元素的播放、暂停和进度状态;
- 样式规则变化;
- 已开启的 Canvas 记录。
这些增量依赖此前的完整快照。例如,增量引用了节点 ID,但对应基线没有成功上传,播放端就无法正确应用这条变化。
4. 分段和上传¶
SDK 会把记录压缩为有序分段。正常录制时,分段通常每约 5 秒、接近内部大小目标、发生 View 切换或页面生命周期变化时刷新。
常规网络失败会按顺序进入内存重试队列。离线、HTTP 408、429 和 5xx 会重试;多数其他 4xx 属于不可重试错误,会使对应分段永久丢弃。
如果某个分段确定无法交付,SDK 会让依赖它的同一基线代次失效,并尝试生成新的完整快照。新快照成功后,后面的页面状态可以继续播放,但永久丢失分段覆盖的操作过程无法补回。
错误回放如何保留错误前数据¶
配置 sessionReplayOnErrorSampleRate 后,命中的 Session 会先在浏览器内存中保存锁定的回放分段。发生错误后,SDK 才会解锁并上传最近的页面基线和增量,并继续录制到 Session 结束。
它提供的是错误前最多约一分钟的上下文,而不是无限历史:
- Session 没有发生错误时,锁定数据不会上传;
- 页面在错误发生前关闭时,内存数据不会进入上传链路;
- 内存缓存达到保护上限时,SDK 会从新的完整快照重新建立可播放基线。
因此,错误回放适合定位错误前操作,不适合替代普通回放保存所有无错误 Session。
DOM 数据什么时候可能缺失¶
浏览器 SDK 为避免会话重放长时间占用主线程,对序列化和缓存设置了保护边界。当前实现中,完整快照最多处理 20 万个节点,估算大小约为 4 MiB;DOM 树深度、单节点属性数、文本长度、样式规则和增量队列也都有边界。
以下场景可能触发截断、丢弃或重新建立基线:
- 页面一次性创建或替换超大 DOM 子树;
- 单个文本、属性或内联样式异常大;
- 高频 DOM 更新持续时间过长,无法得到稳定快照;
- Mutation 增量产生速度持续高于 SDK 的处理速度;
- 页面同时包含大量 Shadow Root、样式规则和超大列表;
- 浏览器读取 DOM 或 CSSOM 本身出现长时间阻塞。
SDK 不会发送半条结构变化。如果一个不可拆分的 DOM 变化过大,会丢弃整条变化并请求新完整快照。这样可以恢复后续页面的一致状态,但不能恢复缺口期间的每个中间步骤。
上述数值是当前 SDK 的内部保护阈值,不是可配置项,也不作为长期稳定的公开 API。排查时应以实际使用的 SDK 版本为准。
隐私规则导致的预期缺失¶
默认隐私级别为 mask-user-input。以下数据可能被遮罩、隐藏或替换为占位内容:
- 密码、邮箱、电话、隐藏输入;
- 信用卡自动填充相关字段;
- 通过隐私属性或隐私类名标记的节点;
shouldMaskNode返回需要屏蔽的自定义节点;- 脚本内容和被隐藏节点的子树。
这类内容缺失属于数据安全策略,不是网络丢包。排查“输入为空”或“某区域没有内容”时,应先检查隐私配置。
HTML 和 Shadow DOM 的能力边界¶
当前采集边界如下:
| 内容 | 采集情况 |
|---|---|
| 普通 DOM | 支持 |
| open Shadow DOM | 支持可访问的结构、变化和相关样式 |
| closed Shadow DOM | 无法保证采集 |
| Web Components | 取决于 Shadow Root 是否开放,以及内部使用的内容类型 |
| iframe | 可记录 iframe 元素本身,不记录其内部文档用于完整还原 |
| video / audio | 可记录元素和播放状态,不采集媒体音轨或逐帧视频内容 |
Canvas 和 WebGL 为什么会掉帧¶
Canvas 录制默认关闭。必须满足以下条件:
- Session Replay 已命中采样并正在录制;
replayCanvasEnabled: true;- 目标 Canvas 已进入可采集的 DOM;
- WebGL/WebGL2 场景已额外注册 WebGL Replay 插件。
Canvas 自动录制采用有预算的调度,不承诺逐帧采集。以下情况可能出现视觉帧缺口:
- 动画速度高于采集节奏;
- 页面隐藏,自动调度暂停;
- 多个 Canvas 等待公平轮转或并发编码;
- 画面未变化而进入退避;
- Canvas 尺寸、编码结果或单次命令过大;
- Canvas 尚未连接到 DOM,或其 DOM 基线尚未发布;
- WebGL 插件在引擎创建 Context、缓存绘制方法之后才初始化;
- 最后一次 WebGL 绘制发生在冷却或编码期间,之后没有新的绘制触发采集。
WebGL Replay 是由绘制驱动的受预算像素快照,不是 WebGL 命令级重放,也不是逐帧视频。详细配置与性能边界见如何接入 Canvas 录制。
页面关闭时为什么容易丢失尾部¶
回放的编码结果和重试队列都只保存在浏览器内存中。页面隐藏、冻结或卸载时,SDK 会主动刷新待处理记录,并尝试通过 sendBeacon 或 XHR 交付。
退出阶段仍然存在以下限制:
- 浏览器留给 JavaScript 的时间很短;
- 退出阶段有有界的发送预算,待发送数据过多时尾部会被丢弃;
sendBeacon()返回成功只表示浏览器接收了排队任务,不代表服务端已经持久化;- 强杀浏览器、浏览器崩溃、移动系统回收进程、断电或网络断开会直接清除内存队列;
- 页面关闭前尚未解锁的错误回放数据不会上传。
不要把关键数据的可靠交付完全依赖于 beforeunload。尽早开始录制、让页面在关键操作后保留正常发送时间,比增加离页逻辑更有效。
数据已上传,为什么播放仍不完整¶
会话重放按 DOM 和资源地址重建页面,不会把所有图片、字体和外部样式完整打包到回放数据中。即使回放分段全部上传成功,仍可能因为以下原因显示异常:
- 图片、字体或样式资源已下线或地址已变化;
- 资源需要登录态、签名或内网访问;
- CORS 不允许播放页面加载资源;
- CSP 阻止 Worker、Blob URL 或资源加载;
- 跨域样式表的 CSSOM 不可读取,只能在播放时重新请求原始链接;
- 平台侧数据处理、索引或回放文件生成尚未完成或失败。
资源问题通常表现为 DOM 结构仍在,但字体、图片、布局或悬停样式不正确。
数据缺口分类¶
| 分类 | 常见原因 | 后续能否继续 |
|---|---|---|
| 未录制 | 未调用开始 API、未采样、浏览器不支持 | 成功开始后可记录,之前无法补回 |
| 开头缺失 | SDK 初始化或开始录制过晚 | 后续可继续,开头无法补回 |
| 隐私遮罩 | 默认隐私规则或自定义屏蔽 | 属于预期行为,不应恢复原文 |
| 能力边界 | closed Shadow DOM、iframe 内文档、媒体内容、Canvas 未开启 | 不支持的数据无法补回 |
| DOM 保护边界 | 页面过大、持续抖动、增量队列溢出 | 新完整快照后可恢复一致,缺口过程无法补回 |
| Canvas 调度边界 | 冷却、退避、隐藏页、编码或尺寸限制 | 后续快照可恢复当前画面,不保证中间帧 |
| Worker/编码失败 | CSP、Worker 创建失败、编码异常或持续背压 | 重新开始录制后可能恢复 |
| 网络永久失败 | 不可重试 4xx、内存队列满、代理拦截 |
新基线后可继续,旧分段无法补回 |
| 页面突然关闭 | 强杀、崩溃、系统回收、退出时间不足 | 尾部通常无法补回 |
| 资源加载失败 | CORS、鉴权、资源过期或 CSP | 修复资源可访问性后可能改善显示 |
| 平台处理异常 | 上传成功但处理或文件生成失败 | 取决于服务端处理结果 |
如何排查¶
建议按以下顺序检查:
- 录制资格:确认采样率、是否调用
startSessionReplayRecording(),并检查isRecording()。 - Session 和 View:执行
getInternalContext(),确认 Application ID、Session ID 和 View ID 存在且符合预期。 - 客户端请求:在浏览器开发者工具中过滤
/v1/write/rum/replay,记录请求时间、状态码、响应和失败原因。 - Worker 与安全策略:检查控制台中的 Worker、Blob URL、CSP 和 Canvas 编码错误。
- 隐私与能力边界:确认缺失区域是否属于遮罩、closed Shadow DOM、iframe、音视频或未开启的 Canvas。
- 资源可访问性:从回放环境验证图片、字体和 CSS URL 的有效期、鉴权、CORS 和 CSP。
- 平台处理状态:如果客户端请求成功但仍无数据,记录 Session ID、Application ID、发生时间、SDK 版本和请求响应后继续排查。
降低数据缺口的建议¶
- 尽早初始化 SDK 并开始录制;
- 根据业务量设置明确的普通回放和错误回放采样率;
- 避免一次性写入超大 DOM 子树、文本、属性和内联样式;
- 大型列表和高频页面状态分批更新;
- 为图片、字体和 CSS 提供稳定地址,并正确配置 CORS 和 CSP;
- 只在需要的页面开启 Canvas,根据场景选择手动、自动快照或高还原度模式;
- 在 WebGL 引擎启动前初始化 WebGL Replay 插件;
- 监控
/v1/write/rum/replay的4xx、429、5xx和网络失败; - 排查时保留 Session ID、View ID、SDK 版本、浏览器版本和页面生命周期信息。