12 · 前端可观测性与线上排障
本篇解决什么问题: 前端上线后是黑盒——用户报错你不知道、白屏你不知道、慢你也不知道。 本篇讲清 错误监控 → Source Map 还原 → 埋点 → 白屏检测 → 告警 → 排障方法论, 让你在用户投诉之前发现问题。
📌 核心认知:测试只能保证"已想到的场景",监控才能发现"没想到的场景"。
开篇速览
| 能力 | 解决什么 |
|---|---|
| 错误监控 | JS 报错、Promise 未捕获、资源加载失败 |
| Source Map | 把压缩后的报错还原到源码行 |
| 性能监控(RUM) | 真实用户的 LCP/INP/CLS(见 A25) |
| 埋点 | 用户行为与转化 |
| 白屏检测 | 页面挂了但没报错的情况 |
| 告警 | 出问题第一时间知道 |
一、错误监控:捕获什么
1.1 五类必须捕获的错误
ts
// ① JS 运行时错误(同步)
window.addEventListener('error', e => {
report({ type: 'error', message: e.message, stack: e.error?.stack, filename: e.filename })
})
// ② 未处理的 Promise rejection(**最容易漏**)
window.addEventListener('unhandledrejection', e => {
report({ type: 'unhandledrejection', reason: String(e.reason), stack: e.reason?.stack })
})
// ③ 资源加载失败(img/script/link)—— 注意它**不冒泡**,要 capture 阶段
window.addEventListener('error', e => {
const t = e.target as HTMLElement
if (t && t !== window && (t.tagName === 'IMG' || t.tagName === 'SCRIPT' || t.tagName === 'LINK')) {
report({ type: 'resource', tag: t.tagName, url: (t as any).src || (t as any).href })
}
}, true) // ✅ 必须 capture: true
// ④ 框架错误边界(React ErrorBoundary / Vue app.config.errorHandler)
// ⑤ 接口错误(拦截器统一上报:状态码、URL、耗时)要点:
| 要点 | 说明 |
|---|---|
unhandledrejection 必须加 | 否则 async 里的错误全部丢失 |
| 资源错误要用 capture | error 事件在资源上不冒泡 |
| 采集上下文 | UA、URL、用户 ID、release 版本、时间 |
| 采样与去重 | 高频错误要采样,否则打满配额 |
1.2 用成熟方案(推荐)
| 方案 | 特点 |
|---|---|
| Sentry | 开源可自托管,功能最全(错误 + 性能 + Session Replay) |
| 商业 RUM | 阿里云 ARMS、腾讯云 RUM、Datadog 等 |
| 自建 | 采集 SDK + 上报接口 + 存储 + 展示(成本很高) |
ts
// Sentry 最小接入
import * as Sentry from '@sentry/react'
Sentry.init({
dsn: 'https://xxx@yyy/1',
release: __BUILD_INFO__.sha, // ✅ 关联版本,才能定位是哪次发布引入的
environment: import.meta.env.MODE,
tracesSampleRate: 0.1, // 性能采样 10%
beforeSend(event) { /* 脱敏 */ return event },
})🎯
release字段极其重要:没有它,你无法把错误关联到具体的发布版本, 也就无法判断"是不是这次上线搞的"。
二、Source Map:还原压缩代码的报错
问题:生产代码是压缩的,报错是 TypeError: Cannot read properties of undefined (reading 'x') at min.js:1:28374——没法看。
解法:上传 Source Map。
ts
// vite.config.ts
export default defineConfig({
build: { sourcemap: 'hidden' }, // ✅ 'hidden':生成 map 但不在产物里暴露引用
})bash
# 构建后上传到 Sentry(然后**删除产物里的 map**,不要发到 CDN)
npx @sentry/cli sourcemaps upload --release=$SHA ./dist关键:
| 要点 | 说明 |
|---|---|
用 hidden | 生成 .map 但 HTML/JS 里不引用它 → 用户拿不到,只有监控平台能还原 |
| 不要把 map 部署到 CDN | 等于把源码公开(常见安全事故) |
| release 必须匹配 | 上传时的 release 与 SDK 初始化的一致,否则还原失败 |
| 上传后删除 | CI 里上传完就删掉本地 map |
⚠️ 安全提醒:Source Map 泄漏 = 源码泄漏。很多公司因此被爬走业务逻辑。
三、埋点:行为数据
3.1 三类埋点
| 类型 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 代码埋点 | 手动在关键位置调用 track() | 准确 | 侵入代码、易漏 |
| 可视化埋点 | 平台圈选元素 | 无需改代码 | 只适合简单点击 |
| 全埋点(无埋点) | 自动采集所有点击/页面浏览 | 全面 | 数据量巨大,噪音多 |
推荐:核心转化路径用代码埋点(下单、注册),其余用全埋点补充。
3.2 埋点设计要点
ts
track('order_submit', {
order_id, amount, coupon_used,
// ✅ 带上必要的业务上下文,否则事后无法分析
})| 原则 | 说明 |
|---|---|
| 统一命名规范 | 对象_动作(button_click / order_submit) |
| 带上业务 ID | 否则无法与后端数据关联 |
| 不要采集敏感信息 | 密码、身份证、完整卡号(合规红线) |
| 控制事件数量 | 每个事件都要有人看,否则是浪费 |
⚠️ 合规:采集用户数据需符合《个人信息保护法》与 GDPR—— 要有隐私政策、用户同意、数据脱敏、可删除。这是法律要求,不是可选项。
四、白屏检测:页面挂了但没报错
场景:JS 执行到一半卡住、渲染条件不满足、接口全挂——没有任何 JS 错误,但页面空白。
4.1 三种检测方式
ts
// ① 根节点内容检测(最简单)
function checkBlank() {
const root = document.getElementById('root')
const isBlank = !root || root.children.length === 0 ||
(root.innerText || '').trim().length < 10
if (isBlank) report({ type: 'blank', url: location.href })
}
setTimeout(checkBlank, 3000) // 3 秒后检测(给渲染留时间)
// ② 关键点位采样:检测若干"必须有"的元素是否存在
const KEY_SELECTORS = ['.app-header', '.main-content']
// 缺少 → 判定异常
// ③ 更稳健:MutationObserver + 首屏渲染时间补充手段:DOM 快照上报(把 #root.innerHTML 截断后上报,便于还原现场)。
五、告警:别让监控变成"没人看的仪表盘"
| 原则 | 说明 |
|---|---|
| 告警要能行动 | 收到告警就知道该干什么,否则等于噪音 |
| 分级 | P0(电话/短信):白屏率、下单失败率飙升;P1(群通知):错误率上升;P2(日报):趋势 |
| 设置合理阈值 | 用相对变化(比昨天同时段涨 5 倍)而非绝对值 |
| 新版本发布后加强观察 | 发布后 30 分钟内重点盯 |
| 抑制重复告警 | 同一错误聚合,不要刷屏 |
必须监控的核心指标:
| 指标 | 说明 |
|---|---|
| JS 错误率 | 错误 PV / 总 PV |
| 白屏率 | 白屏 PV / 总 PV |
| 接口失败率 | 4xx/5xx 占比 |
| LCP / INP P75 | 真实体验(见 A25) |
| 关键转化率 | 下单/注册(断了要立刻知道) |
六、线上排障方法论
① 接到反馈 → 先**复现**(确认环境:设备/浏览器/版本/网络)
② 看监控 → 错误率、白屏率是否飙升?**从哪个 release 开始的?**
③ 缩小范围:
- 是不是刚发布?(是 → 优先回滚,见 A16)
- 集中在某个浏览器/机型?(兼容性)
- 集中在某个地区/运营商?(CDN/网络)
- 集中某个用户群?(灰度开关)
④ 看具体报错 → 用 Source Map 还原到源码行
⑤ 本地复现(同版本 checkout + 同环境)
⑥ 修复 + **补一个回归用例**(见 A11)
⑦ 复盘:监控为什么没提前发现?补上6.1 常用排查技巧
| 技巧 | 做法 |
|---|---|
| Session Replay | 看用户操作的录屏(Sentry 等支持),最直观 |
| Breadcrumbs | 错误前的操作轨迹(点击、路由、请求) |
| 按 release 分组 | 快速定位是哪次发布引入 |
| 用户 ID 关联 | 拉取某个用户的所有日志 |
| 比对法 | 线上正常/异常两个版本的差异 |
自检清单
- [ ] 能说出 5 类必须捕获的错误,以及资源错误为什么要用 capture
- [ ] 知道
unhandledrejection与release字段的重要性 - [ ] 能正确配置 Source Map(
hidden+ 上传 + 不部署到 CDN) - [ ] 能实现白屏检测,并知道核心监控指标有哪些
- [ ] 能按"复现 → 看监控 → 缩小范围 → 定位 → 修复 + 补用例"的流程排障
常见误区
| ❌ 误区 | ✅ 事实 |
|---|---|
"有 window.onerror 就够了" | 漏掉 Promise rejection 和资源加载失败 |
| "Source Map 传上去就行" | 传到 CDN = 源码泄漏;要用 hidden 且不部署 |
| "埋点越多越好" | 没人看的埋点是浪费,且增加合规风险 |
| "监控配好就完事" | 没有告警规则的监控 = 没人看的仪表盘 |
| "线上问题先查代码" | 先确认是否与发布相关,该回滚就先回滚止损 |
动手练习
- 给项目接入错误监控(Sentry 或自建上报),验证 5 类错误都能捕获(尤其 Promise rejection 与图片 404)。
- 配置
sourcemap: 'hidden'并上传,故意在源码里造一个错误,验证能在平台看到原始源码行,且 CDN 上拿不到.map。 - 写一个白屏检测脚本,3 秒后检查根节点,验证"清空 #root"时能被捕获。
相关文档
- 性能指标与诊断 → A25 · 前端性能度量与诊断
- 发布与回滚 → A16 · CI/CD 与部署运维
- 回归保护 → A11 · 前端测试策略
- 安全与合规(脱敏、敏感信息)→ A08 · 前端安全