Skip to main content
线上白屏又复现不了的时候,我一般先看监控里有没有对应的错误和录屏,这比坐着猜快得多。

Q1: 前端监控的历史?为什么要有它?

1

蛮荒时代: window.onerror

最早的前端监控非常原始——开发者在页面中挂一个 window.onerror 全局错误监听,把错误信息通过 new Image() 发到后端。这种方式只能捕获同步的 JS 运行时错误,无法处理 Promise 异常、资源加载失败,更没有 Source Map 还原能力。
2

埋点时代: 自研 SDK 与手动上报

随着业务复杂度提升,团队开始自研埋点 SDK,通过在代码中手动调用 tracker.send() 上报用户行为(PV、UV、点击事件)。这个阶段的核心痛点是埋点覆盖率低——漏埋、错埋是常态,而且 SDK 本身可能影响页面性能。
3

自动化时代: Sentry 与商业 APM

Sentry 的出现改变了游戏规则。它通过全局错误捕获 + Source Map 还原 + Session Replay,让开发者能在线上精准还原用户崩溃现场。同期,Fundebug、阿里 ARMS 等商业 APM 产品也提供了开箱即用的错误监控、性能监控和行为分析能力。
4

全链路时代: 可观测性三支柱

现代前端监控已经演进为可观测性(Observability)体系,包含三大支柱:Logs(日志)、Metrics(指标)、Traces(链路追踪)。前端通过 Performance API 采集 Core Web Vitals(Core Web Vitals 指 LCP、INP、CLS;FCP、TTFB 等属于其他性能指标),结合 OpenTelemetry 标准打通前后端链路追踪。
前端监控解决的核心问题:
前端监控让你从”被动等用户投诉”变成”主动发现问题并精准定位”。

没有前端监控

线上出了 JS 错误,开发者完全不知道。直到用户投诉客服,客服转给产品经理,产品经理再转给开发——这时候可能已经过去好几天,影响了大量用户。

引入前端监控

JS 错误发生的瞬间,Sentry 就推送告警到钉钉群。开发者点开告警链接,Source Map 还原出错误代码行,Session Replay 回放用户操作路径,快速定位问题并修复。
回答时先说”为什么需要监控”(线上问题不可复现的痛点),再说”监控的演进历史”(从 window.onerror 到 Sentry 到全链路可观测性)。想听的是你对线上质量保障的体系化思考,而不是背诵 API。

Q2: MutationObserver 详解

MutationObserver 的本质是异步观察 DOM 树的变化,当 DOM 节点被增删改时触发回调。它替代了已废弃的 Mutation Events(DOMNodeInserted 等)。
MutationObserver 的回调是微任务——浏览器不会在每次 DOM 变化后立即触发回调,而是将所有变化记录(MutationRecord)批量传递给回调。这意味着:
  • 一次同步代码中对 DOM 做了 100 次修改,回调只触发一次,传入 100 条 MutationRecord。
  • 回调执行时机在当前宏任务结束后的微任务阶段。
  • 这与 Vue 的 nextTick 机制有密切关系。

MutationObserver 与 Vue 响应式系统的关系

Vue 2 的 nextTick 内部优先使用 microtask(Promise.then),在不支持的降级方案中使用 MutationObserver 作为微任务的 polyfill。Vue 3 统一使用 Promise.then。

性能优化

MutationObserver 本身性能很好,但回调中的处理逻辑如果不当,可能引发性能问题:

Q3: rrweb 详解

rrweb(record and replay the web)是一个开源的 Web 录制与回放库,核心能力是将用户在页面上的所有操作(DOM 变化、鼠标移动、输入、滚动)录制下来,然后在另一个环境中精确回放。
1

全量快照(Snapshot)

录制开始时,rrweb 递归遍历整个 DOM 树,将每个节点序列化为 JSON 格式。这个快照包含了完整的页面状态:节点类型、属性、文本内容、CSS 样式、Canvas 内容等。
2

增量快照(Incremental Snapshot)

快照之后,rrweb 通过 MutationObserver 监听 DOM 变化,通过各类事件监听器捕获用户交互(鼠标移动、滚动、输入、Resize 等)。每个变化都被记录为一个带时间戳的事件。
3

事件流序列化

所有事件按时间顺序组成一个 JSON 数组(事件流),上传到后端存储。每个事件包含:时间戳、事件类型(Mutation/MouseMove/Input/Scroll 等)、具体数据。
4

回放重建

回放端加载事件流,先用全量快照重建初始 DOM,然后按时间戳依次执行增量事件,精确还原用户的操作过程。回放端还会模拟鼠标指针移动、输入框打字动画等视觉效果。

rrweb 与 Sentry Replay 的关系

Sentry 的 Session Replay 功能底层就是基于 rrweb 实现的。Sentry 在 rrweb 的基础上做了以下增强:
  • 压缩与采样:对事件流进行 gzip 压缩,支持按采样率上报,降低带宽消耗。
  • 隐私保护:默认对输入框内容做脱敏处理(masking),支持自定义隐私规则。
  • 性能优化:对高频事件(如 mousemove)做节流,减少事件流体积。
  • 关联错误上下文:将录屏数据与 Sentry 错误事件关联,点击错误即可回放崩溃前的用户操作。

使用场景

rrweb 序列化原理详解

rrweb 全量快照的核心是将 DOM 树递归序列化为一棵 JSON 树。每个节点被映射为一个 SerializedNode 对象,包含 type、tagName、attributes、childNodes 等字段。
节点类型映射表: 属性过滤规则: rrweb 在序列化属性时会过滤掉事件处理器(onclick、onerror 等),因为这些是函数引用,无法序列化为 JSON。对于 src、href 等资源 URL,rrweb 会将其转为绝对路径。对于 <link rel="stylesheet">,rrweb 会直接序列化计算后的 CSS 规则(getComputedStyle),确保回放时样式一致。

增量快照的事件类型

rrweb 的增量快照覆盖了多类事件,常见的事件类型包括:

rrweb 的性能优化

rrweb 在生产环境中面临的核心挑战是事件流体积和录制性能开销。以下是它采用的优化策略: 采样策略:
压缩算法: rrweb 本身不做 gzip 压缩(由传输层负责),但在数据结构层面做了极致压缩: 事件流体积控制(生产级方案):

rrweb 的隐私保护

Session Replay 涉及用户隐私数据,rrweb 提供了多层隐私保护机制:
在欧盟 GDPR 和中国《个人信息保护法》框架下,Session Replay 必须:
  1. 告知用户:在隐私政策中明确说明会进行录屏回放。
  2. 获得同意:用户必须主动同意(opt-in),不能默认开启。
  3. 数据脱敏:对 PII(个人可识别信息)必须脱敏后才能存储。
  4. 数据生命周期:录屏数据必须有明确的保留期限,过期自动删除。
Sentry 默认开启 maskAllText 和 maskAllInputs,并提供 blockSelector 等拦截能力,开发者需要显式关闭脱敏才能录制明文内容。

rrweb 2.0 新特性

rrweb 2.0 在 1.x 的基础上做了重大升级,解决了之前无法录制 Shadow DOM、Canvas 和 iframe 的痛点:

如何自己实现一个简易版 rrweb

理解 rrweb 核心原理的最好方式是亲手实现一个最小版本。以下是 100 行代码的简化实现:
rrweb 的核心是”全量快照 + 增量事件流”:Snapshot 序列化整个 DOM 树为 JSON,增量快照通过 MutationObserver + 事件监听捕获所有变化,两者结合实现精确的录屏回放。隐私保护(maskAllInputs)和性能优化(采样 + 压缩)是生产环境的两大关键。

Q4: 前端监控系统搭建思路

一个完整的前端监控系统包含两大核心模块:错误监控和行为监控,外加数据上报策略、SDK 设计和后台架构。

错误监控

行为监控

数据上报策略

Core Web Vitals 上报

CWV 是字段数据(field data)——必须在真实用户浏览器里测,而不是在本地 Lighthouse 跑分。推荐用官方的 web-vitals 库,它已经处理好了各指标的计算口径和浏览器差异。
CWV 大多只在页面生命周期结束前才“定型”(例如 LCP 可能随用户滚动继续更新)。因此在 visibilitychange 变为 hidden 时用 sendBeacon 发送,既拿到最终值,又不阻塞页面卸载。不要用 unload 事件,它在移动端不可靠。

监控 SDK 设计

一个生产级的前端监控 SDK 需要解决 3 个核心问题:采集什么(自动 vs 手动)、怎么上报(批量 + 重试 + 离线)、怎么控制开销(采样 + 去重)。

Source Map 还原

线上代码经过压缩后,错误堆栈指向的是压缩文件(app.min.js:1:23456),无法直接定位源码。Source Map 还原是将压缩代码的错误位置映射回源码位置的关键能力。
还原原理: Source Map 文件(.map)包含一个 VLQ 编码的映射表,记录了压缩文件中每个字符位置对应的源文件、源行号、源列号。还原工具读取这个映射表,将压缩位置翻译为源码位置。 两种还原方式:

监控后台设计

一个完整的监控后台需要 4 个核心模块:
错误聚合的核心算法: 将相同根因的错误归为一组,避免开发者面对成千上万条重复错误。
告警规则示例:
前端监控 SDK = 采样策略(控制开销) + 批量上报(减少请求) + 失败重试(数据不丢) + 离线缓存(兜底方案),Source Map 还原是精准定位线上 Bug 的关键能力。

Q5: 高频面试题

答题要点: 从 Vue 2 的 nextTick 降级策略切入,说明 MutationObserver 作为微任务 polyfill 的角色。高分回答: “Vue 2 的 nextTick 需要在当前 DOM 更新完成后执行回调,本质上是利用微任务的特性。Vue 2 内部有一个优先级链:Promise.then > MutationObserver > setImmediate > setTimeout。MutationObserver 的回调是微任务,所以在不支持 Promise 的旧浏览器中,Vue 2 用它作为微任务的 polyfill。具体做法是创建一个文本节点,通过修改文本节点的 data 属性来触发 MutationObserver 回调。Vue 3 统一使用 Promise.then,不再需要 MutationObserver 降级。但理解这个关系很重要——它说明了 MutationObserver 在浏览器任务模型中的位置:它是微任务的一种来源。”
答题要点: 必须讲清楚 Snapshot + Incremental Snapshot 两阶段,以及底层依赖的浏览器 API。高分回答: “rrweb 的录制分为两个阶段:第一阶段:全量快照(Snapshot)。 录制开始时,rrweb 递归遍历整个 DOM 树,将每个节点序列化为 JSON。序列化内容包括节点类型、属性、文本内容、计算样式、Canvas 内容等。这相当于给页面拍了一张’照片’。第二阶段:增量快照(Incremental Snapshot)。 快照之后,rrweb 通过 MutationObserver 监听所有 DOM 变化(增删节点、属性修改、文本变化),通过事件监听器捕获用户交互(鼠标移动、滚动、输入、Resize)。每个变化都记录为一个带时间戳的事件。所有事件按时间顺序组成一个 JSON 数组上传到后端。回放时先用全量快照重建初始 DOM,再按时间戳依次执行增量事件,精确还原用户操作。Sentry 的 Session Replay 就是基于 rrweb 实现的。”
答题要点: 从错误监控、行为监控、数据上报、SDK 设计四个维度给出完整方案。高分回答: “搭建前端监控系统需要覆盖四个层面:1. 错误监控: window.onerror 捕获 JS 运行时错误,unhandledrejection 捕获 Promise 未处理异常,error 事件(捕获阶段)捕获资源加载失败,fetch/XMLHttpRequest 拦截捕获 HTTP 错误。2. 行为监控: 事件委托自动采集点击事件,监听 pushState/replaceState/popstate 捕获路由变化,记录页面停留时长。3. 数据上报: 首选 navigator.sendBeacon()(页面关闭时不丢数据),高频事件做批量上报(攒够 N 条或每隔 T 秒),降级方案用 Image(1x1) 或 fetch keepalive。4. SDK 设计: 采样率控制(sampleRate)、beforeSend 钩子过滤敏感数据、失败重试 + 离线缓存兜底。
答题要点: 从浏览器保证机制、兼容性、请求大小限制三个维度对比。高分回答: “sendBeacon 和 fetch keepalive 都能在页面关闭后完成请求发送,但有重要区别:
  1. 浏览器保证级别不同。 sendBeacon 是浏览器专门为’页面卸载时数据上报’设计的 API,浏览器会优先保证它发送成功。fetch keepalive 是 fetch 的一个选项,保证级别不如 sendBeacon。
  2. 请求大小限制。 sendBeacon 限制请求体大小为 64KB。fetch keepalive 限制为 64KB(Chrome)。
  3. 请求方法。 sendBeacon 只支持 POST。fetch keepalive 支持所有方法。
实际项目中的推荐策略:优先用 sendBeacon,降级用 fetch keepalive,再降级用 Image(1x1)。
答题要点: 说明 VLQ 编码映射表的工作原理,以及服务端还原 vs 前端还原的区别。高分回答: “线上代码经过压缩后,错误堆栈指向的是压缩文件(app.min.js:1:23456),无法直接定位源码。Source Map 文件(.map)包含一个 VLQ 编码的映射表,记录了压缩文件中每个字符位置对应的源文件、源行号、源列号。还原工具读取这个映射表,将压缩位置翻译为源码位置。比如 app.min.js:1:23456 还原为 src/components/UserCard.tsx:42:15。两种还原方式:
  • 服务端还原(推荐): 后端收到错误堆栈后,读取 Source Map 文件做映射。Source Map 不暴露给前端,安全性高。Sentry 就是这个方案——构建时上传 .map 文件到 Sentry 服务端,收到错误时自动还原。
  • 前端还原: 浏览器通过 //# sourceMappingURL 加载 .map 文件。仅用于开发调试,生产环境不要暴露 .map 文件,否则源码泄露。”
答题要点: 说明采样的必要性(控制开销和成本),以及不同场景的采样率设计。高分回答: “全量上报会导致:1)网络带宽浪费,2)后端存储成本暴增,3)SDK 本身影响页面性能。所以必须做采样。采样率设计原则:
  • 错误监控: 一律全量上报,不采样。线上 Bug 不能遗漏。
  • 性能监控: 采样率可高一些。性能数据是统计指标,需要足够样本量才能算分位数,具体比例按日活和后端成本定。
  • 行为监控: 采样率可以更低。点击、曝光等行为数据量巨大,低采样率即能反映趋势。
这些比例没有标准答案,取决于业务流量、存储成本和指标置信度要求,面试时说清“为什么这样分层”比背具体数字更重要。实现方式: 在采样决定点用 Math.random() < sampleRate 判断是否上报。也可以在初始化时一次性决定是否注册某类采集,避免每条事件都做一次随机判断(少一点运行时开销)。
答题要点: 说明 beforeSend 在数据脱敏、过滤噪音、补充上下文三个场景的作用。高分回答: “beforeSend 是监控 SDK 在数据上报前的最后一个拦截点,有三个核心用途:1. 过滤噪音: 过滤掉无价值的错误。比如跨域脚本的 Script error. 没有堆栈信息,对排查问题毫无帮助,可以直接返回 null 过滤掉。2. 数据脱敏: 在上报前对敏感信息做脱敏。比如 URL 中的用户 ID、token 等参数,可以替换为 *** 后再上报。3. 补充上下文: 给错误数据附加业务上下文。比如当前登录用户 ID、页面版本号、灰度分组等,方便后续在监控后台按维度筛选和聚合。
答题要点: 说明 Sentry 基于 rrweb 的增强,以及生产环境中的关键优化。高分回答: “Sentry 的 Session Replay 底层就是基于 rrweb 实现的,但在此基础上做了大量生产级增强:1. 压缩与采样: 对事件流进行 gzip 压缩,支持按采样率只录制部分用户的 Session,降低带宽消耗。2. 隐私保护: 默认开启 maskAllInputs: true,对输入框内容做脱敏处理。支持自定义隐私规则。3. 性能优化: 对高频事件(如 mousemove)做节流,减少事件流体积。录屏体积与页面 DOM 复杂度、交互频率、录制时长都相关,实际以压缩后的事件流大小为准。4. 关联错误上下文: 这是 Sentry 最大的优势——将录屏数据与错误事件关联。用户报 Bug 后,开发者在 Sentry 后台点击错误,直接回放崩溃前的用户操作路径,精准还原 Bug 现场。简单来说:rrweb 提供了录制回放的基础能力,Sentry 在此基础上解决了隐私、性能、存储和错误关联等生产环境问题。“

延伸阅读与引用参考