📡 前端错误监控从零搭建:window.onerror 与 unhandledrejection 的完整实践

14 阅读2分钟

问题场景

线上用户反馈"页面白屏了",你本地怎么复现都不行;或者用户说"点按钮没反应",但你根本不知道报了什么错。没有错误监控的前端,就像蒙着眼睛开车——全靠用户当你的眼睛。

原因分析

前端错误主要分三类,捕获方式完全不同:

  1. 运行时错误(同步/异步代码抛错)→ window.onerror
  2. Promise 未捕获拒绝 → unhandledrejection
  3. 资源加载失败(图片/脚本/CSS)→ error 事件的捕获阶段

很多人只监听 window.onerror,结果 Promise 错误和资源错误全漏了。

完整监控方案

第一步:捕获运行时错误

window.onerror = function (message, source, lineno, colno, error) {
  report({
    type: 'runtime',
    message,
    source,
    lineno,
    colno,
    stack: error?.stack,
    url: location.href,
    ua: navigator.userAgent,
    time: Date.now()
  });
  // 注意:返回 true 会阻止浏览器默认处理(如控制台报错),慎用
};

第二步:捕获 Promise 错误(最容易漏)

window.addEventListener('unhandledrejection', (e) => {
  // e.reason 可能是 Error 对象,也可能是任意值(如字符串、对象)
  report({
    type: 'promise',
    message: e.reason?.message || String(e.reason),
    stack: e.reason?.stack,
    time: Date.now()
  });
  e.preventDefault(); // 可选:屏蔽控制台红色警告
});

第三步:捕获资源加载失败

// 必须用捕获阶段(第三个参数 true),因为资源错误不会冒泡
window.addEventListener('error', (e) => {
  const target = e.target;
  if (target && (target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK')) {
    report({
      type: 'resource',
      tag: target.tagName,
      src: target.src || target.href,
      time: Date.now()
    });
  }
}, true);

第四步:框架层兜底

Vue 3:

app.config.errorHandler = (err, instance, info) => {
  report({ type: 'vue', message: err.message, stack: err.stack, info });
};

React: 用 Error Boundary 包裹,componentDidCatch 里上报。

第五步:上报策略(防刷爆后端)

// 采样 + 去重 + 批量
let buffer = [];
function report(data) {
  buffer.push(data);
  if (buffer.length >= 10) flush();
}
function flush() {
  if (!buffer.length) return;
  // 用 sendBeacon,页面关闭也能发出;且不阻塞主线程
  navigator.sendBeacon('/api/error-report', JSON.stringify(buffer));
  buffer = [];
}
window.addEventListener('beforeunload', flush);

// 采样:只上报 10% 的重复错误,控制成本
if (Math.random() > 0.1) return;

进阶技巧

  • sourcemap 还原:生产代码是压缩的,上报 error.stack 后,用 sourcemap 在服务端还原原始行号(webpack 配 devtool: 'hidden-source-map',map 文件不上传到 CDN)
  • 错误去重:同一错误一天内只告警一次,按 message + source + lineno 做指纹
  • 白屏检测:document.readyState 长时间非 complete 或 document.body 无子节点,主动上报

要点总结

  • 三类错误三种捕获姿势:onerror(运行时)、unhandledrejection(Promise)、捕获阶段 error(资源)
  • 框架错误要用各自的钩子(Vue errorHandler / React Error Boundary)
  • sendBeacon + 批量上报,页面销毁也不丢数据
  • 采样、去重、指纹告警,否则监控系统会被错误洪流淹没
  • sourcemap 只上线 map 文件到内网,别把源码泄露到 CDN