前端错误追踪实战:从捕获到分析的完整方案

司徒子武 优化 阅读 632
赞 30 收藏
二维码
手机扫码查看
反馈

先上核心代码,别整那些虚的

项目上线后用户反馈“页面打不开”“点按钮没反应”,但你本地跑得飞起——这时候,错误追踪就是救命稻草。我一开始也懒得搞,直到某天半夜被运营电话叫醒:“线上支付失败了!” 打开控制台一片空白,连错在哪都不知道。折腾半天发现是某个第三方 SDK 抛了异常,但没被捕获。从那以后,我给自己定规矩:新项目必须接入错误追踪。

前端错误追踪实战:从捕获到分析的完整方案

亲测有效的方式是用 try...catch + 全局监听 + 上报服务。下面这段代码,我用了三年,改过七八次,现在基本稳定:

// 错误上报函数
function reportError(error, info = {}) {
  const payload = {
    message: error.message || String(error),
    stack: error.stack,
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: Date.now(),
    ...info
  };

  // 发送错误(这里用 fetch,实际项目建议用 navigator.sendBeacon)
  fetch('https://jztheme.com/api/error-report', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  }).catch(() => {
    // 上报失败就默默放弃,别再抛错
  });
}

// 全局未捕获异常监听
window.addEventListener('error', (event) => {
  reportError(event.error, { type: 'global-error' });
});

// Promise 拒绝未处理监听
window.addEventListener('unhandledrejection', (event) => {
  reportError(event.reason, { type: 'unhandled-rejection' });
  event.preventDefault(); // 防止控制台报红(可选)
});

这段代码能捕获绝大多数运行时错误。注意,fetch 在上报失败时不能抛错,否则会陷入无限循环——我踩过这个坑,直接把服务器打挂了。

异步错误、Promise 拒绝,一个都不能少

很多人以为加个 window.onerror 就万事大吉,结果 Promise 里 reject 的错误根本收不到。上面代码里特意加了 unhandledrejection 监听,这是关键。但要注意:一旦你手动 .catch() 了 Promise,它就不会触发这个事件。所以团队要统一规范:要么全局兜底,要么每个 async 函数都 try-catch。

比如这个常见场景:

async function fetchData() {
  try {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error('API failed');
    return res.json();
  } catch (err) {
    // 如果这里不 rethrow,unhandledrejection 就收不到
    // 所以要么在这里上报,要么让错误继续抛
    reportError(err, { context: 'fetchData' });
    throw err; // 或者返回默认值,看业务需求
  }
}

建议:在业务层尽量不要吞掉错误,除非你明确知道后果。否则追踪系统里永远看不到这类问题。

踩坑提醒:这三点一定注意

  • 跨域脚本的错误全是 Script error. —— 这是最烦的。解决方法有两个:一是给 script 标签加 crossorigin 属性,二是确保 CDN 返回的响应头包含 Access-Control-Allow-Origin: *。我之前用的静态资源托管没配 CORS,所有错误都变成 “Script error.”,排查了两天才定位到是跨域问题。
  • 不要在错误处理里再抛错 —— 上面代码里 reportError 用了 .catch(() => {}) 就是这个原因。曾经有个同事在上报逻辑里用了未定义的变量,结果每次出错都触发新错误,雪崩式上报,直接把监控后台打爆。
  • 用户隐私和敏感信息 —— 别把用户的 token、手机号、银行卡号通过错误堆栈传出去。有些框架会把组件 props 打印到错误信息里,务必在上报前过滤。比如 React 的错误边界,有时候会把 state 打出来,得手动清洗。

这个场景最好用:结合 Source Map 还原真实代码

生产环境代码都是压缩混淆的,堆栈信息基本没法看。这时候必须配合 Source Map。做法是:构建时生成 map 文件,部署到私有服务器(千万别公开访问!),然后在错误上报平台配置解析规则。

以 Sentry 为例(虽然我后面换了自研方案,但原理一样),它支持自动上传 sourcemap 并解析。如果你自己搭服务,可以这样处理:

// 假设你的错误堆栈是这样的:
// at a (https://jztheme.com/app.12345.js:1:2345)

// 你需要根据文件名和行列号,调用 sourcemap 解析库
// 例如使用 source-map 库(Node.js 环境)
const fs = require('fs');
const { SourceMapConsumer } = require('source-map');

async function parseStack(stack) {
  const consumer = await new SourceMapConsumer(
    fs.readFileSync('app.12345.js.map', 'utf8')
  );
  const original = consumer.originalPositionFor({
    line: 1,
    column: 2345
  });
  // 得到 { source: 'src/utils.js', line: 42, column: 10, name: 'handleClick' }
}

不过说实话,自建 sourcemap 解析挺麻烦的,小团队建议直接用 Sentry 或 Fundebug 这类服务,省心。我之前为了省那点钱自己搞,结果花了一周时间调试,最后还是切回了付费方案——时间比钱贵。

高级技巧:给错误加“上下文”

光知道“哪里错了”不够,还得知道“为什么错”。比如用户点击“提交订单”时出错,你得知道他当时填了什么地址、选了什么商品。这时候可以在关键操作前埋点:

let currentContext = {};

function setContext(key, value) {
  currentContext[key] = value;
}

// 在路由跳转、表单提交等地方设置上下文
router.beforeEach((to) => {
  setContext('route', to.path);
});

document.getElementById('submit-btn').addEventListener('click', () => {
  setContext('form-data', {
    address: document.getElementById('address').value,
    product: selectedProduct
  });
  // ...提交逻辑
});

// 修改上报函数,带上上下文
function reportError(error, info = {}) {
  const payload = {
    // ...原有字段
    context: { ...currentContext },
    ...info
  };
  // ...
}

注意:context 要定期清理,避免内存泄漏。比如页面 unload 时清空,或者限制最大条目数。

最后说点实在的

错误追踪不是一劳永逸的事。我见过太多团队接了 Sentry 就不管了,结果报警邮件堆成山,没人看。建议:初期每天花 10 分钟看下错误面板,快速修复高频问题;等稳定了,再设阈值告警(比如同一错误 1 小时超过 100 次才通知)。

另外,别指望 100% 捕获所有错误。有些浏览器兼容性问题、极端网络中断,可能连上报都发不出去。但只要覆盖 90% 的 JS 异常,就能解决大部分线上问题。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如和性能监控联动、自动化创建 Jira 工单),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流——尤其是怎么低成本做 sourcemap 管理,我还在找更好的方案。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论