资讯动态

rrweb 控制台日志录制插件 @rrweb/rrweb-plugin-console-record:从配置实战到源码级原理与版本演进

发布时间:2026/9/21 2:27:11 来源:尧图企业网站定制
前端可观测性开发工具【免费下载链接】rrwebrecord and replay the web项目地址https://gitcode.com/gh_mirrors/rr/rrweb点击查看免费下载本篇技术指南以 rrweb 官方控制台录制插件rrweb/rrweb-plugin-console-record的 CHANGELOG 为主线结合插件源码、测试用例与官方 console 使用文档完整讲解如何在会话录制中采集console输出、如何配置level/lengthThreshold/stringifyOptions/logger四个核心参数并深入到patch包装、this绑定、console.assert语义、递归防死循环、对象序列化等实现细节帮助读者理解该插件如何工作、为什么这样设计、版本迭代改了什么。一、插件定位把开发者控制台搬进回放画面rrweb 的核心能力是record and replay the web。DOM 快照与增量事件只能还原页面长什么样却无法还原用户操作时代码抛出了什么错误、页面打了哪些日志。rrweb/rrweb-plugin-console-record正是为了解决这个盲区它在录制阶段劫持console的各个方法把日志内容、日志级别、调用堆栈序列化为结构化数据并作为 rrweb 事件输出回放阶段再由配套插件rrweb/rrweb-plugin-console-replay在时间轴上逐条还原这些日志。从项目结构看该插件位于 packages/plugins/rrweb-plugin-console-record/遵循 rrweb v2 的插件协议导出getRecordConsolePlugin(options)工厂函数返回一个实现了RecordPlugin接口的对象可直接放入record({ plugins: [...] })。它的源码仅由三个文件构成职责非常清晰src/index.ts插件入口、initLogObserver观察器与replace劫持逻辑src/stringify.ts把任意 JS 值安全序列化为字符串处理循环引用、bigint、Event、Node、Error等src/error-stack-parser.tsfork 自 stacktracejs 的ErrorStackParser与StackFrame负责解析错误堆栈。二、快速开始用默认配置录制控制台按照 docs/recipes/console.md 的说明启用控制台录制只需把插件加入record的plugins数组import { record } from rrweb/record; import { getRecordConsolePlugin } from rrweb/rrweb-plugin-console-record; record({ emit: function emit(event) { // 注意不要在 emit 中直接调用 console.log应使用原始方法避免递归 const defaultLog console.log[__rrweb_original__] ? console.log[__rrweb_original__] : console.log; defaultLog(event); }, plugins: [getRecordConsolePlugin()], });这里有一个官方文档明确警告的关键陷阱插件会劫持console.log/warn/error等全部方法如果在emit回调里直接调用console.log(event)就会触发已被包装的console.log→ 再次进入录制逻辑 → 无限递归最终抛出Uncaught RangeError: Maximum call stack size exceeded。正确做法是通过console.log[__rrweb_original__]拿到被包装前的原始方法再调用。这个__rrweb_original__属性正是由 rrweb/utils 的 patch 函数 注入的详见下文第四节。三、配置项详解level、lengthThreshold、stringifyOptions、logger插件接受一个可选的LogRecordOptions配置对象官方文档给出了完整参数表key默认值说明level全部 console 方法名[assert,clear,count,countReset,debug,dir,dirxml,error,group,groupCollapsed,groupEnd,info,log,table,time,timeEnd,timeLog,trace,warn]需要录制的 console 级别白名单可只保留需要的级别以减少事件体积lengthThreshold1000单次会话中最多记录的日志条数超过后停止记录并发送一条warn提示stringifyOptions{ stringLengthLimit: undefined, numOfKeysLimit: 50, depthOfLimit: 4 }对象序列化策略stringLengthLimit限制单个字符串值的长度numOfKeysLimit限制对象的键数量上限超出则直接调用其toString()depthOfLimit限制对象嵌套深度防止序列化过深导致浏览器 OOMloggerwindow.console要劫持的 console 对象。可传入其他执行环境的 console 对象例如 iframe 的contentWindow.console实现跨环境录制带自定义配置的完整用法来自官方文档import { record } from rrweb/record; import { getRecordConsolePlugin } from rrweb/rrweb-plugin-console-record; record({ emit: function emit(event) { const defaultLog console.log[__rrweb_original__] ? console.log[__rrweb_original__] : console.log; defaultLog(event); }, plugins: [ getRecordConsolePlugin({ level: [info, log, warn, error], lengthThreshold: 10000, stringifyOptions: { stringLengthLimit: 1000, numOfKeysLimit: 100, depthOfLimit: 1, }, logger: window.console, }), ], });3.1 配置的合并逻辑源码层面从源码看配置合并发生在 src/index.ts 的 initLogObserver 内通过Object.assign({}, defaultLogOptions, options)将用户配置浅合并到默认值上。随后logger参数决定被劫持对象——如果是字符串console则取win[console]win是IWindow即顶层窗口或 iframe 窗口否则直接使用传入的 logger 对象。值得注意的是默认level列表非常完整src/index.ts#L28-L52几乎覆盖了console的全部方法包括group/groupEnd/count/countReset/time/timeEnd/timeLog/table/dir/dirxml/trace/clear。配置层面只做白名单过滤并不额外判断浏览器是否支持某方法——replace内部会通过if (!_logger[level]) return noop跳过不存在的级别src/index.ts#L179-L184因此在不支持某方法的旧浏览器上也不会报错。四、工作原理patch 劫持、this 绑定与防递归4.1 基于 rrweb/utils 的 patch 机制插件对每个待录制级别调用patch该函数原属于插件内部2.0.0 版本将其迁移至rrweb/utils以改善打包见 CHANGELOG 2.0.0 条目。patch 的实现位于 packages/utils/src/index.ts#L290-L330若name in source不存在则返回空清理函数保存original source[name]调用replacement(original)生成包装函数wrapped通过Object.defineProperties(wrapped, { __rrweb_original__: { value: original } })把原始方法挂到包装函数的__rrweb_original__属性上这就是前文emit中救命的原始方法来源用wrapped覆盖source[name]并返回一个恢复函数用于在stop()时还原原始方法。插件侧src/index.ts#L186-L239把日志逻辑封装在replacement返回的包装函数内核心流程是return patch(_logger, level, (original) { return (...args) { original.apply(_logger, args); // 先执行原始 console 方法保证页面行为不受影响 // ... 收集 trace、payload回调 cb }; });4.2this绑定修复CHANGELOG 2.1.3 / 2.1.4 / 2.1.5 核心变更包装函数调用原始方法时使用original.apply(_logger, args)显式将this绑定为 logger 对象。这正是 CHANGELOG 2.1.3 修复的关键问题在更早的版本中如果包装函数以错误的接收者调用原方法在严格上下文例如浏览器扩展的 content script里会抛出TypeError: Illegal invocation。测试 test/this-binding.test.ts 精确复现了这一场景测试构造了一个fakeLogger其log方法会捕获调用时的this并模拟以错误 receiver 调用就抛异常的原生行为随后通过插件包装后调用fakeLogger.log(hello)断言capturedThis fakeLogger从而验证包装函数始终以 logger 为this调用原方法。4.3inStack防递归标志日志被包装后如果stringify序列化过程例如触发 Vue 响应式 Proxy 的 getter反过来调用了某个 console 方法就会产生录日志 → 序列化 → 又打日志 → 又录日志的无限循环。为此包装函数引入inStack标志src/index.ts#L114、src/index.ts#L198-L236进入日志收集逻辑前置inStack true若在inStack期间又有 console 方法被调用直接return丢弃避免无限循环收集结束在finally中复位inStack false。对应的测试用例 should handle recursive console messagestest/index.test.ts#L53-L91构造了一个带 Proxy 的递归对象console.log该对象时会触发 Proxy 的get并再次console.warn测试断言最终快照中只有 1 条 console 日志而不是多条。4.4console.assert语义修正CHANGELOG 2.0.0 核心变更原生console.assert只在断言为假falsy时才输出日志第一个参数只用于判断不参与输出。CHANGELOG 2.0.0PR #1530修正了插件此前的错误行为源码中体现在两处src/index.ts#L193-L196if (level assert !!args[0]) return;—— 断言为真时直接跳过录制src/index.ts#L209-L210const argsForPayload level assert ? args.slice(1) : args;—— 录制 payload 时剔除第一个断言参数。集成测试 test/index.test.ts#L96-L101 用console.assert(0 0, should not log assert)与console.assert(false, should log assert)分别验证真值不记录、假值记录。4.5 堆栈采集与日志条数阈值每条日志都会通过ErrorStackParser.parse(new Error())采集当前调用堆栈并.splice(1)去掉被劫持的 log 函数自身这一帧src/index.ts#L205-L207。error-stack-parser.ts 内维护了 Firefox/Safari 与 Chrome/IE 两套堆栈正则兼容不同浏览器格式。日志计数受lengthThreshold控制当累计记录数小于阈值时正常回调cb恰好等于阈值时发送一条warn级别的提示The number of log records reached the threshold.超过后不再记录src/index.ts#L215-L231。序列化或收集过程中若抛出异常包装函数会回退到原始方法输出rrweb logger error:保证录制逻辑的异常不会破坏页面本身的 console 输出。五、未被劫持的全局错误error 与 unhandledrejection除了劫持 console 方法插件还以事件监听的方式捕获两类不是 console 调用的错误src/index.ts#L117-L166且仅当level配置中包含error时启用error事件捕获全局window上的错误通过ErrorStackParser.parse(error)解析堆栈payload 为错误消息unhandledrejection事件处理未捕获的 Promise rejection。若event.reason是Error实例payload 形如Uncaught (in promise) ${error.name}: ${error.message}否则额外序列化event.reason本身。这两类事件在stop()时通过removeEventListener完整卸载。录制输出的数据结构为type LogData { level: LogLevel; // 日志级别 trace: string[]; // 堆栈帧字符串数组 payload: string[]; // 序列化后的日志内容 };插件标识PLUGIN_NAME rrweb/console1src/index.ts#L243回放端据此识别日志事件。六、序列化策略stringify 的安全细节日志内容必须序列化为字符串才能进入 rrweb 事件流src/stringify.ts 的stringify基于JSON.stringify的 replacer 实现了多种保护循环引用去环fork 自json-stringify-safe遇到循环引用输出[Circular ~]或[Circular ~.path]bigint输出数字加n后缀避免JSON.stringify直接抛错Event对象遍历其属性对数组值如path、composedPath等节点数组用pathToSelector转换成tagName:eq(index)...形式的 CSS 路径选择器stringify.ts#L12-L47避免把整个 DOM 节点序列化进日志Node/HTMLElement元素输出outerHTML其他节点只输出nodeNameError对象优先输出完整stack并附加End of stack for Error object标记超限保护对象键数超过numOfKeysLimit、或嵌套深度超过depthOfLimitisObjTooDeep递归检测depth 为 0 视为超深、或值为function时改走toString()并受stringLengthLimit截断超出部分以...结尾。这套策略同时服务于两个目标控制事件体积numOfKeysLimit/depthOfLimit/stringLengthLimit以及避免序列化过程触发浏览器 OOMdepth 限制即源于 rrweb 的 issue #653。七、配套回放rrweb/rrweb-plugin-console-replay录制只是前半段。若事件流中包含 console 类型日志回放端会自动播放docs/recipes/console.mdimport { Replayer } from rrweb/replay; import { getReplayConsolePlugin } from rrweb/rrweb-plugin-console-replay; const replayer new Replayer(events, { plugins: [ getReplayConsolePlugin({ level: [info, log, warn, error], }), ], }); replayer.play();回放插件的两个选项level默认全部级别指定要回放的日志级别与replayLogger默认是基于 console 的对象可通过实现 ReplayLogger 接口 在模拟浏览器控制台中回放日志例如渲染成开发者工具风格的日志面板。八、版本演进从 CHANGELOG 看插件迭代脉络该插件的 CHANGELOG 完整记录了自 v2.0.0-alpha 以来的演进可归纳为三个主题1. 包拆分与产物格式重构2.0.0随 rrweb 主版本大版本发布插件从 rrweb 主包拆出为独立 npm 包rrweb/rrweb-plugin-console-recordPR #1497与rrweb/packer、rrweb-plugin-console-replay等一批插件一同独立发布发布产物结构变化所有.js文件改为 ES Module现代浏览器、Node.js 与支持 ESM 的打包器可用新增.cjs与.umd.cjs产物后者将全部依赖打成单文件供script标签直接引入额外提供/umd/输出目录以支持带.js扩展名的 UMD 文件避免与 package.json 中dist 下所有 .js 均为模块的约定冲突PR #1704。可查看该包的 package.json 中exports/main/module/unpkg/jsdelivr字段确认各产物的入口版本号与rrweb、rrweb/utils保持同步2.0.0 → 2.0.1 → 2.1.x 一路对齐。2. 行为正确性修复2.0.0 / 2.1.3console.assert语义修正只捕获断言为假时的日志PR #1530详见 4.4 节this绑定修复包装函数以 logger 为this调用原始方法消除严格上下文中的 Illegal invocationPR #1904详见 4.2 节。3. 打包与依赖优化patch函数移入rrweb/utils统一管理改善各插件的打包体积与一致性PR #1631peerDependencies声明rrweb: ^2.1.1与rrweb/utils: ^2.1.1与当前版本2.1.5保持兼容。九、测试与验证插件质量如何保证插件的自动化测试集中在 test/ 下由 vitest puppeteer Vite dev server 驱动index.test.tsshould handle recursive console messages验证 Proxy 递归对象场景下不会产生无限循环与重复日志should record console messages覆盖全部 console 级别assert、count、countReset、debug、dir、dirxml、group、groupCollapsed、info、log、table、time、timeEnd、timeLog、trace、warn、clear、Error对象参数以及iframe 内 console 的跨环境录制page.frames()[1].evaluate中调用console.log(from iframe)其快照断言在 test/snapshots/index.test.ts.snapthis-binding.test.ts以 jsdom 环境验证 2.1.3 的this绑定修复详见 4.2 节stringify.test.ts 与 error-stack-parser 相关测试分别覆盖序列化边界与堆栈解析格式。运行插件自身的测试与类型检查# 在 packages/plugins/rrweb-plugin-console-record 目录下 yarn test # vitest run yarn check-types # tsc -noEmit十、使用建议与注意事项emit中务必使用__rrweb_original__这是官方文档反复强调的坑否则必然栈溢出控制事件体积生产环境建议按需裁剪level例如只保留error/warn/log并根据业务对象复杂度设置stringifyOptionsnumOfKeysLimit与depthOfLimit调小、stringLengthLimit设置上限以降低事件存储成本lengthThreshold兜底默认 1000 条足以防止日志洪峰拖垮事件流超出时会收到阈值告警warn事件iframe 场景插件天然支持传入 iframe 的 console 对象跨 iframe 的日志可通过logger参数一并纳入录制相关行为已由测试覆盖版本对齐该插件与rrweb、rrweb/utils同步发版当前 2.1.5升级时建议三者一同升级并留意 CHANGELOG 中标注的破坏性变更如 2.0.0 的产物路径调整若直接引用 dist 文件需改为.umd.cjs等新命名。赞分享前端可观测性开发工具【免费下载链接】rrwebrecord and replay the web项目地址https://gitcode.com/gh_mirrors/rr/rrweb点击查看免费下载相关推荐rrweb Canvas WebRTC 回放插件rrweb/rrweb-plugin-canvas-webrtc-replay 演进与实战指南rrweb Canvas WebRTC 回放插件rrweb/rrweb plugin canvas webrtc replay 演进与实战指南 本篇技术指南前端可观测性开发工具rrweb 控制台录制与回放console 记录/回放插件的配置、原理与实战指南rrweb 控制台录制与回放console 记录/回放插件的配置、原理与实战指南 本篇指南围绕 rrweb 提供的 console 录制与回放插件展开讲解如前端可观测性开发工具rrweb 顺序 ID 回放插件实战rrweb/rrweb-plugin-sequential-id-replay 原理与配置详解rrweb 顺序 ID 回放插件实战rrweb/rrweb plugin sequential id replay 原理与配置详解 在基于 rrweb ht前端可观测性开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价