线上Bug一直复现不了?我用Sentry把错误追踪效率提升了10倍

"用户打电话来说'你们网站崩了',我去 Sentry 看了一眼------显示 0 个错误。" 线上事故就发生了那么一次,但那一次的代价是 3 小时的排查时间。

这不是段子,是我们团队的真实经历。

那次之后我认真研究了一下 Sentry,发现一个扎心的事实:大多数团队的 Sentry 根本就没接对。要么 sourcemap 配错了堆栈全是问号,要么过滤规则太激进把真正的 bug 全过滤掉了,要么压根没自定义事件,出了问题只知道"哪一行代码错了"而不知道"用户怎么走到这一步的"。

这篇文章是我踩坑经验的完整复盘,适合:全栈工程师、独立开发者、以及所有想真正用好 Sentry 的前端团队。


01 Sentry 接入的 3 个常见坑

坑1:Sourcemap 配错,错误堆栈全是问号

Sourcemap 是 Sentry 能准确告诉你"哪一行代码有问题"的关键。没有正确配置的 Sentry,线上报错只能看到压缩后的 app.min.js:1:12345 ------ 根本没法定位。

常见配置错误有两种:

第一种:上传了 sourcemap 但路径不匹配

Sentry 上传 sourcemap 时,需要确保 release 版本号与 SDK 初始化时的一致:

javascript 复制代码
// ❌ 错误:release 版本不一致
Sentry.init({
  dsn: 'https://xxxxx@sentry.io/xxxx',
  release: 'v1.2.3',  // 上传时用 v1.2.3
  // 但如果构建时 release 是 'v1.2.3-prod',就匹配不上
});

正确做法是让构建工具自动注入 release 版本:

javascript 复制代码
// webpack 配置(vue-cli / cra 都适用)
const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');

module.exports = {
  plugins: [
    new SentryWebpackPlugin({
      org: 'your-org',
      project: 'your-project',
      authToken: process.env.SENTRY_AUTH_TOKEN,
      release: process.env.COMMIT_SHA,  // 用 git commit hash
      url: 'https://sentry.io',
      ignore: ['node_modules'],
      configFile: 'sentry.properties',
    }),
  ],
  devtool: 'source-map',  // 确保生成 sourcemap
};

第二种:Sourcemap 内联了但没上传

有些构建配置会把 sourcemap 内联到 JS 文件末尾(sourceMapInline: true),这样浏览器能看但 Sentry 看不到。上传 sourcemap 和内联 sourcemap 只能二选一,推荐上传但不内联

javascript 复制代码
// 生产构建配置
{
  devtool: false,  // 不生成 source-map 文件
  productionSourceMap: true,  // 生成 .map 文件用于上传
}

验证 Sourcemap 是否生效的方法:故意在代码里抛一个带特定消息的错误,然后在 Sentry 后台看到堆栈信息是否精确到源文件 + 行号。如果看到的是 app.min.js:1:12345 就是没配对。


坑2:跨域配置丢失,错误数据不完整

如果你的前端应用有多个域名,或者 API 请求走了不同的子域,Sentry 可能会因为跨域问题抓不到完整的错误上下文。

常见场景:www.example.com 的页面加载了 cdn.example.com 的静态资源,当 cdn 上的 JS 报错时,浏览器的 CORS 策略会阻止 Sentry 获取完整的错误信息。

解决方案:

javascript 复制代码
// 在引入 Sentry SDK 之前,设置全局忽略列表(避免采集第三方脚本错误)
// 同时开启 CORS 采集(确保能拿到跨域脚本的完整堆栈)
Sentry.init({
  dsn: 'https://xxxxx@sentry.io/xxxx',
  // 确保 sentry 自己的脚本不受影响
  allowUrls: [
    /localhost/,
    /example\.com/,  // 你的主域名
    /cdn\.example\.com/,  // cdn 域名
  ],
  // 过滤不想采集的脚本
  ignoreErrors: [
    /ResizeObserver loop/,
    /Non-Error promise rejection captured/,
  ],
  defaultIntegrations: true,  // 确保默认集成都开启
});

另外记得在 index.html 里加一层 Script 加载错误捕获:

javascript 复制代码
window.addEventListener('error', (event) => {
  if (!event.error) return;  // 排除非 Error 对象的错误
  Sentry.captureException(event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  Sentry.captureException(event.reason, {
    extra: { type: 'unhandledrejection' },
  });
});

坑3:过滤规则太激进,真正的 bug 被静默丢弃

很多团队在 Sentry 接入初期,为了"减少噪音",把过滤规则设得过于严格,结果把真正需要关注的 bug 也过滤掉了。

我见过最夸张的配置是这样的:

javascript 复制代码
Sentry.init({
  dsn: 'https://xxxxx@sentry.io/xxxx',
  ignoreErrors: [
    /Script error/,
    /Failed to load resource/,
    /Network request failed/,
    /timeout/,
    /undefined/,
    /null is not/,
    /is not a/,
  ],
});

这些规则看起来在"减少噪音",但实际上:

  • undefined is not a function → 可能是真实的业务逻辑 bug
  • Network request failed → 可能是用户网络问题,也可能是后端 API 故障
  • timeout → 用户支付超时时你也想知道

正确的做法是:先接收所有错误,看两周数据,再决定过滤哪些。

Sentry 后台本身有 "User and Preview" 功能,能帮你分析哪些错误是高频的、哪些是一次性的。过滤要基于数据,而不是拍脑袋。


02 实战案例:定位一个"复现成本极高"的 bug

说个真实案例。

去年我们电商平台的用户反馈:"结算页点了支付,没跳转到微信/支付宝,然后订单就消失了"。客服收到的投诉量不大,一周可能才几单,但每一单都是真实流失。

按传统排查方式:日志查用户 ID → 找订单服务 → 找支付回调 → 判断是哪一步出问题。一套流程走下来,顺利的话半小时,遇到日志不全或者链路复杂的情况,3 小时起步。

用 Sentry 之后,整个排查过程变成了 10 分钟。

关键是我们给支付流程加了自定义事件上下文:

javascript 复制代码
// 支付流程的关键节点打点
async function initiatePayment(orderId, paymentMethod) {
  const span = Sentry.startTransaction({
    op: 'payment',
    name: `发起支付-${paymentMethod}`,
  });

  try {
    // 记录当前状态
    span.setContext('payment_init', {
      order_id: orderId,
      payment_method: paymentMethod,
      user_id: getCurrentUserId(),
      cart_value: getCartTotal(),
    });

    const { payUrl, orderNo } = await api.createOrder({ orderId });

    span.setContext('order_created', {
      order_no: orderNo,
      pay_url_length: payUrl.length,
    });

    // 模拟跳转
    window.location.href = payUrl;

    span.setStatus('ok');
    span.finish();
  } catch (error) {
    span.setStatus('error');
    span.finish();

    Sentry.captureException(error, {
      contexts: {
        payment_flow: {
          order_id: orderId,
          failed_at: 'initiatePayment',
          payment_method: paymentMethod,
        },
      },
    });

    throw error;
  }
}

加了这段之后,Sentry 报上来的错误带着完整上下文:哪个用户的哪笔订单、用了什么支付方式、失败在哪一步、当时的购物车金额是多少。

那次投诉来的时候,我直接在 Sentry 搜索这个用户 ID,看到的错误信息是:

css 复制代码
payment_init: { order_id: "ORD20240615XXX", payment_method: "wechat", user_id: "U12345" }
order_created: { order_no: "W20240615XXX", pay_url_length: 0 }

pay_url_length: 0 ------ 我们发现了问题:后端在特定商品组合时返回了空字符串的支付链接,前端没做校验就直接跳转了。

修复方案:前端加一个非空校验 + 后端修复特定商品组合的支付链接生成逻辑。这个 bug 修完之后,那类投诉再也没出现过。

这就是 Sentry 自定义事件的威力:它不只告诉你"错了",还告诉你"用户是怎么走到这一步的"。


03 自定义事件:让 Sentry 不只报"哪里错了"

很多人用 Sentry 只做了一件事:Sentry.captureException(error)。这当然有用,但它只能记录"已经抛出错误"的场景。

自定义事件的真正价值是:在没出错的地方,也插入埋点。

场景1:追踪关键业务路径

比如电商的"加购 → 下单 → 支付 → 完成"漏斗,你想知道每个环节的转化率和用户在哪个环节流失:

javascript 复制代码
// 加购
Sentry.addBreadcrumb({
  category: 'commerce',
  message: '商品加入购物车',
  level: 'info',
  data: {
    product_id: product.id,
    product_name: product.name,
    price: product.price,
    quantity: quantity,
  },
});

// 下单
Sentry.addBreadcrumb({
  category: 'commerce',
  message: '提交订单',
  level: 'info',
  data: {
    order_id: order.id,
    total_amount: order.total,
    item_count: order.items.length,
  },
});

// 支付回调(不论成功失败都记录)
Sentry.addBreadcrumb({
  category: 'payment',
  message: paymentResult.success ? '支付成功' : '支付失败',
  level: paymentResult.success ? 'info' : 'error',
  data: {
    order_no: orderNo,
    payment_method: paymentMethod,
    callback_status: paymentResult.status,
    error_message: paymentResult.errorMsg,
  },
});

这样当用户投诉"付了钱但没收到货"时,你可以在 Sentry 里直接搜这个用户的 ID,看到完整的操作链路:几点几分加购、几点几分下单、几点几分支付、支付回调返回了什么。

场景2:性能监控(Transactions)

Sentry 不只能捕获错误,还能测量性能:

javascript 复制代码
const transaction = Sentry.startTransaction({
  op: 'pageload',
  name: '首页加载',
});

const span = transaction.startChild({
  op: 'api',
  description: '获取首页数据',
});

await fetchHomeData().catch((err) => {
  Sentry.captureException(err, {
    contexts: { span: { op: 'api', description: '获取首页数据' } },
  });
});

span.finish();
transaction.finish();

在 Sentry 后台的性能页面,你能清晰看到:DNS 解析用了多少 ms、API 请求用了多少 ms、页面渲染用了多少 ms、哪一步是瓶颈。


04 告警阈值设计:哪些错误必须立即叫人,哪些可以静默

告警太多 = 狼来了 = 真正的告警被无视。

我总结了一套分级告警策略:

P0 - 立即叫人(7×24 小时电话)

适用于:支付链路错误、核心功能完全不可用、用户数据泄露风险。

javascript 复制代码
// 触发 P0 告警的错误类型
const P0_ERRORS = [
  'PaymentError',
  'UnauthorizedAccess',
  'DataBaseConnectionFailed',
  'SSLError',
];

这类错误建议设置 Sentry 的 Alert -> Issue Alert -> When... issue matches 'PaymentError' -> Send notification to On-Call

P1 - 工作时间告警(白天 30 分钟响应)

适用于:功能局部不可用、部分用户受影响。

javascript 复制代码
// 触发 P1 告警:某类错误数量 5 分钟内超过 20 次
// 在 Sentry Alert 规则里设置:
// IF count() > 20 IN 5 minutes AND issue.category == "Error"
// THEN send to #frontend-alerts (Slack channel)

P2 - 静默收集,工作日汇总

适用于:用户体验问题(不影响核心功能)、偶发错误、第三方服务小故障。

这类错误不告警,但每周看一次 Sentry 的 "Issue Summary",了解整体错误趋势。

具体阈值设置建议(基于我的经验):

错误类型 告警阈值 通知方式
支付相关错误 ≥1 次 立即通知
登录/注册失败 ≥5 次/10min 立即通知
API 超时 ≥20 次/5min 工作群
页面崩溃(JS Error) ≥10 次/5min 工作群
偶发组件渲染错误 ≥50 次/1h 汇总报告

05 我的 Sentry 配置清单

这部分给一份可以直接复制使用的配置模板,基于 Next.js + Axios 项目:

.env 配置

bash 复制代码
# .env.local
NEXT_PUBLIC_SENTRY_DSN=https://xxxxx@xxxx.ingest.sentry.io/xxxxx
SENTRY_AUTH_TOKEN=sntrys_your_token_here
SENTRY_ORG=your-org-name
SENTRY_PROJECT=your-project-name
COMMIT_SHA=$(git rev-parse --short HEAD)

Sentry 客户端配置(sentry.client.config.js

javascript 复制代码
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  
  // 环境
  environment: process.env.NODE_ENV,
  release: process.env.COMMIT_SHA,

  // 采集范围(只采集自己域名的错误)
  allowUrls: [
    /https:\/\/your-domain\.com\//,
    /localhost/,
  ],

  // 过滤第三方脚本噪音
  ignoreErrors: [
    /ResizeObserver loop/,
    /Non-Error promise rejection captured/,
    /Source map error.*script\.js/,
  ],

  // 采样率(生产环境 100%,开发环境 20%)
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 1.0 : 0.2,

  // 用户信息脱敏(不要把密码、token 传上来)
  beforeSend(event) {
    if (event.user) {
      delete event.user.email;
      delete event.user.username;
      event.user.ip_address = '{{auto}}'; // 使用 IP 但不记录真实地址
    }
    return event;
  },

  // 自定义面包屑上限(防止大量操作刷屏)
  maxBreadcrumbs: 50,
});

API 层统一错误捕获(sentry.server.config.js

javascript 复制代码
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  release: process.env.COMMIT_SHA,

  // 服务端采集所有错误
  tracesSampleRate: 1.0,

  // 过滤不想告警的服务器端错误
  ignoreErrors: [
    /ECONNREFUSED/,  // 服务启动时数据库未就绪
    /No such file or directory/,  // 构建阶段文件缺失
  ],

  beforeSend(event) {
    // 脱敏处理
    if (event.request?.headers) {
      delete event.request.headers['authorization'];
      delete event.request.headers['cookie'];
    }
    return event;
  },
});

Next.js 的 API Route 自动捕获

@sentry/nextjs 会对所有 API Route 自动做 try-catch 包装,不需要手动在每个 API 里写错误捕获。唯一需要手动做的是在有复杂异步逻辑的 API 里增加自定义上下文:

javascript 复制代码
// pages/api/checkout.js
import * as Sentry from '@sentry/nextjs';

export default async function handler(req, res) {
  const transaction = Sentry.startTransaction({
    op: 'api',
    name: 'POST /api/checkout',
  });

  try {
    // 业务逻辑...
    const result = await processCheckout(req.body);
    
    transaction.setStatus('ok');
    res.json({ success: true, orderId: result.orderId });
  } catch (error) {
    transaction.setStatus('error');
    transaction.setContext('checkout_error', {
      user_id: req.body.userId,
      cart_value: req.body.cartTotal,
      error_type: error.name,
    });

    Sentry.captureException(error);
    res.status(500).json({ error: 'Checkout failed' });
  } finally {
    transaction.finish();
  }
}

结语

回到开头那个场景:用户打电话来说网站崩了,你去看 Sentry------这次不会再说" 0 个错误"了。

好的 Sentry 配置,是一个团队工程化成熟度的标志。它不是"装上就行了",而是需要:正确的 sourcemap、合理的过滤规则、关键路径的自定义埋点、以及一套基于业务优先级的告警分级。

配置好这套东西之后,线上问题不再是"用户报故障 -> 研发排查 3 小时",而是"Sentry 告警 -> 点进去看上下文 -> 10 分钟定位问题"。

错误监控不是成本,是保险。你希望永远不用,但不能没有。

相关推荐
Slice_cy1 小时前
对前端工程化的理解
前端
Slice_cy1 小时前
状态机设计理念与实现
前端
星栈1 小时前
LiveView 的生命周期:mount、handle_event 和 Socket 到底怎么运转
前端·前端框架·elixir
yingyima1 小时前
JWT Token 解析与安全实践速查:5 问 5 答直击要害
前端
kyriewen2 小时前
我用 Codex 重写了同事维护三年的代码,他没说谢谢——而是找了领导
前端·javascript·ai编程
OpenTiny社区3 小时前
从零开发 AI 聊天页要两周?试试这款 Vue3 垂直对话组件库 TinyRobot,直接开箱即用
前端·vue.js·github
铁皮饭盒3 小时前
S3已成为文件存储标准,阿里/腾讯/华为云都支持,Bun率先原生支持
前端·javascript·后端
Cobyte3 小时前
22.Vue Vapor 组件 props 的实现
前端·javascript·vue.js
lichenyang4533 小时前
从 has.showToast 看 ASCF 的 API 调用链路
前端