线上工单是程序员最讨厌的东西,尤其是那种“在我这边是好的”“我就是点了几下就出问题了”的描述。之前我排查这类问题只能靠聊天记录截图加后端日志盲猜,偶尔碰上复现步骤带得全的,还得按着用户的操作路径一步步重演,效率低得离谱。后来我把 rrweb 引到项目里,用完整回放去还原用户的操作过程,再配合已有的前端监控做线上 bug 监听,效果完全不一样了。这篇文章我就把整个接入思路、关键配置、回放姿势和踩过的坑都梳理一遍,给同样在做前端稳定性治理的朋友一个可参考的落地模板。
1. 为什么非要用“回放”的方式去定位线上 bug
1.1 光有埋点和日志不够,缺的是“现场”
大多数前端团队现在都上了错误监控,比如把 window.onerror、unhandledrejection 这些全局错误抓到,再附带一个堆栈和 url 上报回来。这套方案能帮我们定位哪一行代码抛了异常,却不能告诉我们用户在那一刻到底触发了什么操作路径,更没法解释为什么同一个页面别人没事、用户偏偏就踩雷。
后端的做法稍微好一点,一般会有请求日志和链路追踪,能看到某个接口响应变慢或者报 500,但用户“怎么一步步走到这个接口”的上下文仍然缺失。比如用户点击了 A 按钮,表格先请求了列表数据,紧接着又触发了一个保存操作,这个先后依赖在前端页面上可能一秒钟就结束了,靠日志是很难还原出来的。
rrweb 这东西补的正是这个“现场”。它不靠手动埋点去编织逻辑,而是通过持续的 DOM 快照和变化记录,把页面上发生的交互过程变成一串可重放的“事件流”。用户当时看见了什么、点了哪里、输入了什么、页面在哪个时刻弹出了报错,全部都能像录像一样在本地回放出来。
1.2 rrweb 记录的是什么,又是怎么做到的
用一句话概括 rrweb 的原理:用录制 DOM 变化事件代替录制视频。它不是一个录屏工具,不靠 canvas.captureStream 去抓画面,而是通过 MutationObserver 监听节点增删改、属性变化,再结合鼠标交互、滚动、输入事件的元数据,生产一连串结构化的记录事件。
这些事件分为几类:
- 全量快照:页面初次加载时记录完整的 DOM 树,作为回放的初始化基线。
- 增量变更:后续每一次节点变更只记录差异,比如某个文本节点从 A 变成 B、某个按钮新增了 disabled 属性。
- 交互事件:鼠标的 mousemove、mousedown、click,以及 input 输入框的字符变化,都会以虚拟坐标和数据值方式记录下来。
- 视图变化:scroll 滚动位置、视口大小变化等。
回放端拿到这些事件后,会先重建一个“轻量文档”,然后把增量变更按时间戳依次 apply 上去,最终把当时的页面状态还原出来。因为保存的是文本和结构而非视频帧,所以单段录制数据的体积相比真正的录屏要小一到两个数量级,这也是它能大规模上生产环境的基础。
1.3 和“录屏软件”的本质区别
有人可能会说,我直接用浏览器录屏不也能排查吗?原生录屏能记录用户电脑上的整个画面,但带来的问题是:数据太大存不了几天、视频不可检索、而且会录进大量与业务无关的信息,隐私风险非常高。
rrweb 的回放是结构化数据,这意味着它可以做三件视频很难做的事情:
- 全文检索:把事件里的文本数据捞出来,比如按用户输入过的关键字去查关联会话。
- 和错误堆栈/接口日志对齐:找到报错发生的时间点,然后精准回放那一段带高亮的操作。
- 按条件抽样或脱敏:URL、文本、图片、输入框内容都可以在录制端做拦截或打码,而不是录完整个视频再去后期处理。
这也是我在实战中最终选择 rrweb 的原因:它不是替代监控,而是把“监控报出一个错误”和“用户实际怎么操作”补成一个完整的因果关系。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目整体设计与改造思路
2.1 从短链路到长链路,回放系统拆成哪几块
回到标题说的“使用 rrweb 还原用户的操作,监听线上 BUG”,这个需求可以拆成三条链路:
- 录制链路:在用户浏览器里自动启动记录,产生事件数据,然后缓冲、压缩、上报。
- 存储链路:接收事件数据,建立会话维度索引,关联用户、请求、错误、时间区间等元数据。
- 回放监听链路:开发者在错误面板看到一个异常上报后,点击“查看用户操作录像”,前端从后端拉取该 session 的事件序列,通过 rrweb-player 渲染回放。
在设计这套系统之前,我先确认了几个关键原则,建议你也照着做:
第一,错误优先上报,事件流异步上传。记录数据不要阻塞用户操作,更不能和接口主链路抢带宽。实测我一般会让事件数据走 sendBeacon 或者 requestIdleCallback 延后发送,错误触发时立刻传一次元信息,再补传事件流。
第二,所有事件数据必须带 sessionId 和 timeOffset。sessionId 用来把同一次访问周期的多段记录穿起来,timeOffset 用来重放时对齐页面节点的加载顺序,以及和后端日志时间戳做交叉比对。
第三,录制要能随时启停。不是所有用户都需要全部录制,我采用的是白名单加规则触发:命中某些关键页面、报错发生前的最近 30 秒、或者用户主动反馈问题时,才把完整事件流保留下来。
2.2 技术选型为什么是 rrweb 而不是自己造轮子
有段时间我也想过,能不能在框架层面自己写一套交互记录器,比如在 Vue 的指令里统一埋点 + 把 action 序列存起来。但真做起来会发现三个绕不开的问题。
一是覆盖面不够。你可以在自己写的 onClick 里埋点,但浏览器自动填充、第三方组件内部交互、原生下拉框选择这些行为很难全部埋到,漏一点回放就会失真。rrweb 直接作用在 DOM 变化这一层,不依赖开发者有没有写埋点代码。
二是回放还原成本极高。就算记录下来了点击事件,要把当时的真实界面还原出来,必须解决样式加载、异步状态、图片缓存等一系列时序问题。rrweb 通过快照基线加增量变更的方式,在录制端就把还原所需的全部信息固化下来了,开发者不需要自己设计一套“虚拟 DOM 存储格式”。
三是社区生态和兼容性。rrweb 已经有 rrweb-player、rrweb-snapshot 等配套库,主流的错误监控平台也支持接入,团队扩容时不需要让每个人都理解很底层的序列化协议,上手成本低很多。
2.3 最终链路长什么样
我在实施时是按下面这条链路来组织模块的:
- 前端在应用初始化时调用
rrweb.record开启录制。 - 每次 emit 出的原始事件先进入内存缓冲队列,队列长度达到 100 条或间隔 5 秒触发一次批量上报。
- 上报接口把事件序列存入后端存储,并更新 session 的最新活跃时间。
- 当项目里的全局错误监听捕获到一个异常,立即把 errorId、堆栈、发生时间、sessionId 关联起来写入“待回放事件表”。
- 前端错误列表页提供一个动作按钮,从后端查询该 session 在错误发生时间前后各取 60 秒的 events,交给回放播放器渲染。
- 开发者查看回放时,右侧同时展示该 session 的 console、network request、后端接口日志时间线,方便交叉定位。
听起来不算复杂,但真正落地的时候,每一环节都有很值得展开的细节。下面一章我把接入步骤和关键参数完整写出来,可以直接拿去参照。
3. 接入步骤与核心参数实践
3.1 前端安装与快速初始化
项目用的如果是 NPM/Vite 这类标准前端工程,先做安装:
bash复制npm install rrweb rrweb-player
初始化放在应用入口文件,不能在组件生命周期里反复调用。要注意 rrweb.record 在同一个页面同一时间应该只启动一次,我见过有人因为 React StrictMode 的开发模式重复挂载 effect,导致录了两份事件流,回放时出现重复鼠标轨道的诡异现象。
javascript复制import { record } from 'rrweb';
let events = [];
let stopFn = null;
function startRecord(sessionId, userId) {
stopFn = record({
emit(event) {
// 统一打上会话标记
event.sessionId = sessionId;
event.userId = userId;
// 先塞到缓冲队列里
pushToBuffer(event);
},
// 如果采集量非常大也可以直接开压缩
// recordAfter: 100
});
}
function stopRecord() {
if (stopFn) {
stopFn();
stopFn = null;
}
}
stopFn 是 rrweb 提供的停止录制函数。这个在单页应用路由切换时不需要调,因为 SPA 的切换本身是 DOM 变化事件,rrweb 会自己记录;但如果是用户关闭页面前,需要触发一次立即 flush,避免还有大量数据堆在内存里没来得及上报。
3.2 关键配置项:脱敏与忽略
把脱敏配置放最前面讲,是因为生产环境最容易在这里踩坑。rrweb 默认会把 input 内容记录下来,如果用户的输入框里有身份证号、地址、支付金额这类隐私数据,直接落库风险非常大。
我实践的脱敏方案如下:
javascript复制record({
emit(event) {
pushToBuffer(event);
},
// 禁止录制指定类名区域的内容,比如验证码图片、密码框外层容器
blockClass: 'rr-block',
// 忽略该类名区域的点击与交互
ignoreClass: 'rr-ignore',
// 隐藏所有输入框的文本值,只保留输入行为
maskAllInputs: true,
maskInputOptions: {
password: true,
email: true,
tel: true,
// 数字输入也脱敏,避免记录用户的金额敏感信息
number: true,
},
maskTextSelector: '.card-number, .id-card, [data-sensitive]',
});
maskAllInputs会把所有输入内容变成*,回放时能看到用户在打字,但看不到具体内容,适合绝大多数业务。blockClass一类操作是直接不采集指定 DOM 子树的序列化数据,回放时这块区域会弹出占位提示,适合嵌了 iframe 或第三方加密控件的场景。maskTextSelector可以用来把特定 CSS 选择器匹配到的文本内容在事件里打码,比如卡号后四位、用户名等。
如果对输入内容没有那么敏感,也可以只开 maskInputOptions 对 password 做脱敏,但我的建议是宁多勿少,因为存储侧的隐私合规比多一个回放字段重要得多。移动端用户经常是后台切走再切回来,如果页面里有快速填充的短信验证码,你开了 tel 脱敏会看到回放时验证码位置是星号,这完全不影响定位逻辑 bug,用户也安心。
3.3 上报策略和会话切分的设计
上报不做节流必然出事,特别是业务复杂的中后台页面,DOM 变更可能非常频繁,数据量大的时候请求会一直占用带宽。
我的缓冲策略是这样的:
javascript复制const buffer = [];
const FLUSH_SIZE = 100;
const FLUSH_INTERVAL = 5000;
function pushToBuffer(event) {
buffer.push(event);
const now = Date.now();
if (buffer.length >= FLUSH_SIZE || !lastFlushTime || now - lastFlushTime >= FLUSH_INTERVAL) {
flushBuffer();
}
}
function flushBuffer() {
if (!buffer.length) return;
const payload = buffer.splice(0, buffer.length);
// 优先走 sendBeacon,页面关闭场景下也能尽量送达
if (navigator.sendBeacon) {
const blob = new Blob([JSON.stringify({ sessionId, events: payload })], { type: 'application/json' });
navigator.sendBeacon('/api/rrweb/events', blob);
} else {
fetch('/api/rrweb/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, events: payload }),
keepalive: true,
});
}
}
会话切分我建议按服务端 session 的生命周期来走。简单做法是:前端登录后拿到一个全局唯一的 traceId,存到 localStorage,页面刷新不换;直到用户登出或者 30 分钟无交互,才由后端在查询 session 列表时把超过阈值的轨迹自动断开。这样既保证“一次问题反馈 = 找到一段完整回放”,又不会因为浏览器切屏、锁屏等因素把同一用户的多个访问记录硬拼成一个巨大 session。
3.4 回放侧接入:rrweb-player 初始化
做回放页面时不需要自己跟 DOM 结构打交道,直接用官方播放器组件就行:
bash复制npm install rrweb-player
页面级组件里这样挂载:
javascript复制import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
function initPlayer(container, events) {
const player = new rrwebPlayer({
target: container,
props: {
events,
width: 960,
height: 540,
// 回放倍速调整组件
showController: true,
autoPlay: false,
// 跳到错误发生的几秒前
speed: 1,
},
});
player.addEventListener('start', () => {
console.log('回放开始');
});
return player;
}
把后端查出来的 events 数组原样传给 props 就行。注意事件数组必须按 timestamp 字段升序排列,因为 rrweb-player 从底层 fastly 模式重建时是按时间 slice 的,如果顺序乱掉回放画面会跳变。
播放器 UI 本身支持倍速、暂停、跳转进度条,但对开发者的实际使用体验来说,最重要的是再包一层“直接跳到错误发生点”的逻辑。我一般会在后端把异常时间点一起返回,前端根据异常时间戳在 events 里二分查找最近的一帧,然后调用 player.goto(time, true) 让播放器直接定位到报错前后 5 秒,省去拖动进度条的时间。
3.5 错误监听如何和回放事件打通
监控线上 bug 的第二步不是看回放本身,而是先建立“错误 → session → 事件片段”的关联表。我在全局错误监听里做了这样的埋点:
javascript复制window.addEventListener('error', (event) => {
reportIssue({
type: 'js',
message: event.message,
stack: event.error && event.error.stack,
url: location.href,
line: event.lineno,
col: event.colno,
sessionId: currentSessionId,
timestamp: Date.now(),
});
});
window.addEventListener('unhandledrejection', (event) => {
reportIssue({
type: 'promise',
message: event.reason && event.reason.message,
stack: event.reason && event.reason.stack,
url: location.href,
sessionId: currentSessionId,
timestamp: Date.now(),
});
});
上报接口落两张表:一张是错误堆栈表,去重同一类错误,记录出现次数、影响用户数;另一张是错误回放关联表,把每一条实际发生的错误记录对到某个 sessionId 和时间点。
之后开发者在查看面板点击某条错误时,后端就能用这段伪代码完成定位:
sql复制SELECT session_id, happened_at
FROM error_occurrences
WHERE error_group_id = ?
LIMIT 1;
-- 然后取出该 session 在 happened_at - 30s 到 happened_at + 30s 内的 events
SELECT events
FROM rrweb_sessions
WHERE session_id = ?
AND event_timestamp BETWEEN ? AND ?;
这样错误列表点开之后直接播放的就不是一整个小时的无聊操作,而是命中问题前那关键的几十秒,分析效率会快很多。
4. 回放数据与前后端 bug 的匹配排查
4.1 回放里能看到什么“线索”
有一次运营后台反馈,说部分用户上传 Excel 文件后一直卡在“正在解析”的 loading 状态,后端日志却没有对应的任务记录。光看后端日志确实没法定位,因为问题可能出在文件上传对象被意外清空、或者前端在某个中断条件下没把请求真正发出去。
我打开错误关联里的回放,事件序列还原出来的画面显示:用户先打开了上传弹窗,然后点击了“下载标准模板”,接着在系统的二次确认框弹出时没等它完全关闭,又迅速选择了文件并点击上传。这时操作焦点在确认框上,文件上传请求其实被浏览器拦截了,但页面没有给出任何提示。
回放把“在哪个黑屏/阻塞层上点击”和“输入框焦点变化”都记录了下来,这种细节你在后端日志里是永远查不到的。它能快速暴露一类“事件顺序竞态”问题——真实用户的操作可不会像测试脚本那样等待每个弹窗消失再点下一步。
4.2 如何区分前端问题还是后端问题
把这个单独拿出来说,是因为“如何区分前后端 bug”是排查中最脏最累的活儿,rrweb 回放数据恰好能提供几个客观证据点。
- 如果回放正常走完了整条交互,但某个接口返回了 4xx/5xx:大概率后端问题或者前后端契约不匹配。看回放能确认前端没有少传参数、没有错误跳转。
- 如果回放在点击“保存”按钮后,按钮变成 loading 再也没恢复:说明前端收到了响应但没正确处理,或者请求一直 pending。重点查超时设置、拦截器、Promise 状态的死锁。
- 如果回放在某个字段输入后,页面出现红框校验错误,和用户描述不一致:那就是前端校验逻辑的边界情况,跟后端没关系,回放里看到的错误提示就是最直接的用户视角。
我在实践中的一个经验是:把回放的数据流和 network 记录做成时间轴对齐图。每一次点击都对应后续的接口请求,假如点击后没有发出请求,问题大概率出在前端事件绑定或表单校验;假如请求出去了但迟迟没有响应,就需要后端配合看日志了。
4.3 基于回放的“监听”机制延伸:用户反馈一键回放
监控线上 bug 除了被动等全局 error 捕获,还可以主动留一个“用户反馈问题”入口。当用户点击“我要反馈”时,前端把错误反馈表单里的描述内容和当前 session 的最近 30 秒事件打包,直接提交到一个新问题工单里。
这在处理那种“用户自己也描述不清楚”的问题时特别好用。以前客服要反复问用户“你填了什么内容、点击了哪个按钮、页面有没有报错”,现在回放一看:哦,用户在某个下拉框里选了不存在于当前权限范围内的选项,然后页面崩溃之后自动刷新了。
我们在引导台长期开着这个功能,把反馈入口做成一个小浮标,文案叫“遇到了问题?留下轨迹。”用户点反馈时默认不收集输入内容,只传页面路径和最近 60 秒事件,用户同意后才把敏感字段脱敏后的完整数据附上。上线一个月后,“不可复现 bug”的工单闭环率提高了大概三成,而且用户对“留轨迹”这个操作的接受程度比很多人预想的高,因为表单里说明了“不会记录密码和验证码”。
5. 上线后必然踩到的坑与排查技巧
5.1 性能问题:数据量爆炸
接完 rrweb 后最大的坑就是数据量在高峰期会突然飙升。有客户的一个页面是实时行情仪表盘,每秒都在更新涨跌数字和 K 线图。这段 DOM 变化在 rweb 里会转为大量增量事件,我们统计过单个用户的单日事件量能超过 30 万条,后端存储成本直接警告。
应对措施分三层:
- 录制端抽样:对于实时刷新的纯数字文本变化,可以用
sampling配置来降低采样频率,或者在关键数据区域加rr-block,只保留操作而不记录实时数值。 - 事件压缩:rrweb 的一个事件往往有比较高的重复率,可以使用 gzip 对 payload 做压缩再上传,实测能将网络传输体积缩小 60% 左右。
- 存储侧分级:完整事件只保留 7 天,错误关联的片段保留 30 天,超期的直接归档到冷存储。
超过 1 分钟无交互的“静止画面”也可以做剪辑。默认 rrweb 会持续记录 mousemove,但用户去开其他页面或者看文档时,鼠标长时间不动,这里会有大量空转事件。我建议实现一个简单的心跳机制:如果超过 15 秒没有产生任何输入和 DOM 变化,就暂停录制;一旦检测到交互,再恢复,并在事件里插入一个时间跳跃标记。回放播放器遇到这个标记会自动快进,数据量能省下来大约 20% 到 40%。
5.2 跨域和 iframe 场景记录不全
如果业务页面里嵌入了第三方 iframe,rrweb 默认是拿不到 iframe 内部 DOM 结构的,回放时这块区域往往是空的。有次排查一个和地图组件相关的问题,回放里地图区域白茫茫一片,根本看不出用户拖拽到了哪里。
我的处理方式分为两种:
- 同源 iframe:用
rrweb.record({ iframe: true })选项,并确保子页面也加载了 rrweb,再通过 postMessage 合并事件流。 - 跨域 iframe:无法录制内部 DOM,只能退而在回放画面上标注“此处为外部应用”,结合外层页面当时的容器位置和尺寸变化来推断操作。
实测下来,同样源 iframe 合并是比较稳的,跨域就只能靠截图锚点等辅助手段。如果跨域内容是业务的关键路径,建议优先推动改成同源嵌入或者用 postMessage 把关键操作透传出来。
5.3 回放画面和真实页面不一致
回放并不是 100% 像素级还原,兼容性问题主要出在下面几类:
- 字体缺失:用户本机装了某种字体,回放环境没装,布局就会轻微错位。好在 rrweb 采用虚拟 DOM 渲染,只用相对定位,大部分场景不会影响判断。
- 图片跨域:如果页面上有跨域图片且没有配 CORS,快照会把图片记录成一个占位标签,回放时图片加载不了。解决办法是把图片 URL 通过后端代理转存到自己的对象存储。
- CSS 动画和视频:rrweb 不会记录 WebGL canvas 的实际画面(虽然新版本实验性支持 Canvas 录制,但性能消耗比较大),遇到动画类组件,回放里看到的多半是“最后一帧状态”。
遇到这类“回放和截图不一致”的反馈,我的排查技巧是:在播放器旁边放一个“回放对比模式”,把关键时间点的原始页面截图(由业务侧在真实用户环境里截取)和回放画面放一起对比,能立刻看出是录制缺失还是预期差异。当然这需要额外埋点,只在页面关键步骤触发。
5.4 上报服务端的接口需要考虑鉴权与限流
事件上报接口很容易被刷或被人为构造,也需要防止内部权限不足的开发误拉某个用户的敏感操作会话。这块我直接列一下建议:
- 上报接口只接受同源且有用户态的 POST 请求,验证 origin 和 token。
- 事件查询接口必须做权限校验,只有具备“问题处理”角色的账号才能查看带敏感数据的回放。
- 存储层按 sessionId 分桶,防止一次性拉取超大事件流把内存打满。
- 在接口层限制单次查询最大事件数和时间窗口,比如不超过 10 分钟、不超过 5 万条。
权限管理是容易忽视但线上事故高发的一环。曾经有内部测试账号被员工拿到,直接查了某用户的长会话回放,下拉框里的输入内容虽然脱敏了,但页面结构里一些带注释的测试数据还是看得出端倪。后来我们把回放数据的明文查询全部加了审计,访问记录留痕。
5.5 多端兼容与移动端注意
PC 端的实践相对成熟,移动端用 rrweb 时要注意:
- 屏幕尺寸差异大,事件里记录的是相对坐标,播放器需要自适应宽度,不然回放画面会变形。
- 移动端浏览器在切后台时,页面被冻结,定时器不执行,缓冲队列上报会严重延后。我采用的兜底方案是在
visibilitychange事件里立即 flush。 - iOS 的 WKWebView 对
sendBeacon的支持限制比较多,Payload 大小不能太大,必要时切成fetch keepalive和分块上报。 - 虚拟键盘弹出会导致可视区域高度变化,这类变化 rrweb 会当成视口变化记录下来,回放时会看到页面轻微跳动,不影响定位,但分析师第一次看到会以为出了 bug。
6. 复盘与更深入的结合方式
6.1 回放数据反哺 bug 生命周期管理
我一直觉得回放系统最好用的不是“单次看 bug”,而是把回放产出沉淀成团队的 bug 知识库。当一类报错被验证为前端状态管理问题后,把回放链接和备注附到这个错误分组上。下次同样的错误聚合触发时,维护者点开的历史备注里直接有上次的回放片段和修复方案,不用再从零分析。
我按这个思路维护了一个“bug 类型总结文档”,在回放记录里打标签,比如:
- 时序竞态
- 权限控制未刷新
- 表单校验边界
- 接口超时未做错误态
- 多标签页同步问题
每个标签下挂 1 到 3 条回放片段。新同学接手工单的第一件事变成了“看同类型回放再复现”,而不是上来就翻代码上下文,培养成本降得很明显。
6.2 和后端日志做痕迹关联
回放事件流只覆盖浏览器端,要定位一个复杂 bug 全貌,必须结合后端的 traceId。做法是在前端发起每次请求时,在 header 里带上当前 sessionId 或者一个请求对应的 traceId,后端日志框架把这些字段透传到日志系统里。
这样当回放到某一步点击“保存”却没有发生预期请求时,开发可以按时间戳和 traceId 去关联查询后端是否有日志输出。如果没有日志但前端发出了请求,那可能是网络层被断开或网关拦截;如果后端有日志但返回错误,那就能很快收敛到业务逻辑问题。
这个“前端回放 + 后端日志”的联动方式不需要很重的基础设施,能在已有监控几乎不动的情况下加上一层判断纬度。
6.3 后续扩展:从回放升级到自动化问题识别
回放数据积累到一定规模后,纯人肉看录像也是成本。我给回放录制数据加了一层轻量化的事件模式抽取:把一个 session 内的点击序列、路由变化、输入焦点变化抽成行为向量,再去做同类事件的聚类分析。
比方说,当你发现 30 个用户都在“列表页搜索 → 打开详情 → 点击付款 → 页面白屏”这个操作链路上崩溃了,这就不再是偶发问题,而是一个高概率的线上 bug 模式。这事现在多数平台自身不带,但回放数据本身是齐全的,只要在存储侧多花一点功夫写好事件分析任务,就能自动产出“影响用户、影响页面、操作路径相似度”这类比堆栈去重更实用的报警指标。
最后分享一段我的体会
我踩过几次坑之后最大的感受是:回放不是神药,它解决不了“错误为什么发生”的根因分析,但它是把用户操作的“黑盒”打开的最直接手段。以前遇到线上 bug,团队的讨论往往陷入“你复现一下”“我这边好的”的循环;现在有了 rrweb 提供的用户侧操作路径回放,讨论起点变成了“我们把回放看一遍再说”,效率提高了不是一星半点。
如果你是刚开始做接入,我建议不要第一次就铺全量。选一个反馈量最大的核心业务页面,先开白名单录制,配合错误弹窗加上“查看最近回放”的入口。跑通一条最小闭环后,再逐步把存储策略、脱敏策略、会话关联做完善。回放这类基础设施最怕一口气铺太大,反而会因为数据量、隐私问题失控而被叫停。先把链路走通,再让数据反哺团队,才是长期能坚持下来的姿势。如果你在接入过程中也遇到什么奇怪的坑,欢迎在评论区一起聊聊。
