Skip to content

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 里的错误全部丢失
资源错误要用 captureerror 事件在资源上不冒泡
采集上下文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 且不部署
"埋点越多越好"没人看的埋点是浪费,且增加合规风险
"监控配好就完事"没有告警规则的监控 = 没人看的仪表盘
"线上问题先查代码"先确认是否与发布相关,该回滚就先回滚止损

动手练习 ​

  1. 给项目接入错误监控(Sentry 或自建上报),验证 5 类错误都能捕获(尤其 Promise rejection 与图片 404)。
  2. 配置 sourcemap: 'hidden' 并上传,故意在源码里造一个错误,验证能在平台看到原始源码行,且 CDN 上拿不到 .map。
  3. 写一个白屏检测脚本,3 秒后检查根节点,验证"清空 #root"时能被捕获。

相关文档 ​