使用 rrweb 还原用户的操作,监听线上 BUG

哥们最害怕的时刻莫过于:测试环境一切正常,一上线用户就报错。

更糟糕的是,用户反馈往往只有一句:“页面打不开了”或者“点击没反应”。当我们试图复现时,却发现自己无论怎么操作都无法触发 Bug。用户不愿意提供详细步骤,客服也传达不清楚,最后只能对着日志干瞪眼。

如果有这样一种技术,能像“时光倒流”一样,完整还原用户出错前的每一步操作,那该多好?


一、为什么我们需要监控与回放?

1.1 沉默的流失

在产品运营中,愿意主动上报 Bug 的用户是极少数。 绝大多数用户遇到体验问题或 Bug 时,选择是直接关闭页面,卸载应用,然后永远不再回来。我们失去了挽留他们的机会,甚至不知道他们为什么离开。

1.2 “在我这里没问题”

前端开发的口头禅:“我没复现这个问题啊,tmd用户怎么操作的”。 因为线上环境太复杂了:

  • 用户的网络波动
  • 特定的浏览器版本
  • 特殊的操作顺序
  • 并发请求的竞争条件

1.3 解决方案:从“猜”到“看”

传统的日志监控(如 Sentry)只能告诉我们哪里错了(Error Stack),但很难告诉我们用户是怎么操作才导致错的会话回放技术填补了这个空白。


二、什么是 rrweb?

rrweb (record and replay web) 是一个开源的 Web 会话录制与回放库。

2.1 它不是视频录屏

很多人以为 rrweb 是像 OBS 一样录制视频流。这么想你就错了

  • 视频录屏: 录制像素点,文件大,无法检索,无法分析 DOM 结构。
  • rrweb 回放: 录制 DOM 变更事件(Mutation)。它记录的是“在什么时间,哪个节点,发生了什么变化”。

2.2 核心优势

  1. 体积极小: 传输的是 JSON 文本,经过压缩后,几分钟的操作可能只有几十 KB。
  2. 可检索: 因为记录的是 DOM 结构,你可以搜索页面上的文字来定位片段。
  3. 隐私可控: 可以在录制时配置忽略敏感输入框(如密码、银行卡号)。
  4. 跨平台: 录制的文件可以在任何地方回放,甚至离线回放。

三、核心原理:DOM 的序列化与反序列化

rrweb 的工作原理可以概括为:“把页面上的状态变成 JSON 数据,然后拿着这个 JSON 数据再转换成页面”。

3.1 录制阶段(Record)

rrweb 在用户浏览器中运行,主要依赖以下技术:

  1. MutationObserver: 监听 DOM 树的变化(节点新增、删除、属性修改、文本变化)。
  2. 事件监听: 监听鼠标移动、点击、滚动、输入等交互事件。
  3. 快照(Snapshot): 在录制开始时,通过遍历 DOM 树生成一份初始 HTML 结构的 JSON 快照。
  4. 增量数据: 之后只记录变化的部分(Incremental Data)。

数据流示例:

// 初始快照 { "type": 2, "data": { "node": { "tagName": "html", ... } } } // 增量操作:用户点击了按钮 { "type": 3, "data": { "source": 1, "positions": [{ "x": 100, "y": 200 }] } } // 增量操作:DOM 发生变化 { "type": 3, "data": { "source": 2, "adds": [...], "removes": [...] } }

3.2 回放阶段(Replay)

  1. 创建一个干净的 iframe 或容器。
  2. 读取初始快照,重建 DOM 树。
  3. 按照时间戳顺序,依次重放增量事件(模拟鼠标移动、触发 DOM 变更)。
  4. 我们排查问题的时候看到的就像是在看视频一样。

四、架构设计与实施步骤

要实现一套完整的回放系统,我们需要三部分组成:客户端 SDK数据存储服务回放管理平台

4.1 基本流程

  1. 用户端: 用户访问页面 -> SDK 自动录制 -> 触发异常或会话结束 -> 数据上报。
  2. 服务端: 接收数据 -> 压缩存储(如 MongoDB 或 OSS)。
  3. 管理端: 开发人员登录后台 -> 查看错误列表 -> 点击“查看回放” -> 加载数据播放。

4.2 代码实现 Demo

npm 地址 : https://www.npmjs.com/package/rrweb

1. 安装 rrweb

npm install rrweb

2. 前端录制与上报

import rrweb from 'rrweb'; // 开始录制 rrweb.record({   emit(event) {     // 将事件数据发送到你的服务器     // 建议:不要每个事件都发,要在本地缓存,攒够一定数量或时间后批量发送     fetch('/api/record', {       method: 'POST',       headers: { 'Content-Type': 'application/json' },       body: JSON.stringify({ events: [event], sessionId: getSessionId() })     });   },   // 隐私保护:忽略包含 .sensitive 类的元素   blockClass: 'sensitive',    // 忽略脚本标签   ignoreClass: 'ignore', }); // 模拟获取会话 ID,用于关联错误日志 function getSessionId() {   return localStorage.getItem('session_id') || generateUUID(); }

3. 后端接收(Node.js 示例)

app.post('/api/record', (req, res) => {   const { events, sessionId } = req.body;   // 将 events 存入数据库,key 为 sessionId   db.save(sessionId, events);    res.send({ success: true }); });

4. 管理端回放

import rrweb from 'rrweb'; // 从服务器获取录制数据 const events = await fetch(`/api/replay?sessionId=${id}`).then(r => r.json()); new rrweb.Player({   target: document.getElementById('replay-container'),   data: events, });

五、关键局限与解决方案(避坑指南)

虽然 rrweb 很强大,但它不是万能的。“它只记录了用户的界面操作,没法监控到具体出了什么问题,它无法录制 js 里面的代码。”

5.1 局限一:无法直接捕获 JS 错误堆栈

rrweb 记录的是“现象”,不是“病因”。如果页面白屏了,回放可能只看到页面加载了一半然后不动了,但不知道是哪个变量 undefined 导致的。

解决方案

  • rrweb + 错误监控 (把报错信息一起传到日志中):
    1. 当 JS 报错时,错误监控 SDK 捕获错误堆栈。
    2. 将当前的 sessionId 附带在错误日志中一起上报。
    3. 在查看错误详情时,提供一个“查看回放”的按钮,通过 sessionId 拉起 rrweb 回放。 这样你就既知道了*为什么错(堆栈),又知道了怎么错的(操作路径)。*

5.2 局限二:性能开销

全量录制所有 DOM 变化和鼠标移动,可能会占用用户主线程,导致页面卡顿。

解决方案:

  1. 采样录制: 只对 10% 的用户开启录制,或者仅在检测到错误时开启(需配合预加载)。
  2. 节流(Throttle): 限制鼠标移动事件的录制频率(如每 500ms 记录一次)。
  3. 暂停录制: 当页面切到后台(visibilitychange)时暂停录制。

5.3 局限三:隐私安全

如果不小心录下了用户的密码、身份证号或聊天记录,会引发严重的法律合规问题(GDPR/个人信息保护法)。

 解决方案:

  1. 配置 blockClass 强制将敏感输入框标记为不录制(rrweb 默认会隐藏 input[type="password"],但其他敏感信息需手动配置)。
  2. 数据脱敏: 在上传前,对 JSON 数据中的特定字段进行清洗。
  3. 访问控制: 回放平台必须权限严格,只有授权人员可查看,且查看日志需留痕。

5.4 局限四:数据量与存储

高频操作会产生大量 JSON 数据。

解决方案:

  1. 压缩: 前端使用 pako (gzip) 压缩后再上传。
  2. 生命周期: 设置数据保留期限(如只保留 7 天),过期自动删除。
  3. 触发式上传: 平时只存在本地 IndexedDB,只有发生 Error 或用户主动反馈时,才上传之前的录制数据。

六、总结

前端监控体系的建设,是衡量一个团队工程化成熟度的重要标志。

  • 错误监控(Sentry 等) 告诉我们 What(出了什么错)。
  • 会话回放(rrweb) 告诉我们 How(用户怎么操作的)。
  • 性能监控 告诉我们 Where(哪里慢)。

rrweb 集成到我们的监控体系中,它能极大地缩短 Bug 排查时间,减少开发与测试的沟通成本,更重要的是,它能让我们真正站在用户的视角去理解产品体验。

最后提醒: 技术虽好,隐私先行。在上线回放功能前,请务必更新隐私协议,并获得用户的同意。

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk