"用户打电话来说'你们网站崩了',我去 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→ 可能是真实的业务逻辑 bugNetwork 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 分钟定位问题"。
错误监控不是成本,是保险。你希望永远不用,但不能没有。