React 错误追踪:Sentry 集成实战
用户遇到白屏但你不知道——Sentry 在用户遇到错误前就帮你抓到。本文讲清前端错误追踪:Sentry SDK 集成、Error Boundary 捕获、source map 上传(生产定位到源码行)、release 追踪、性能监控、隐私脱敏。
本文要点
- 主题聚焦:Next.js、React
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
用户反馈"页面白屏了",你第一反应是什么?登机器翻日志,发现前端代码跑在用户浏览器里,你的服务器一个字都没记。问用户"控制台什么报错",对方回你一张白屏截图,连 F12 是什么都不知道。等你在本地复现出来,已经是三天后,影响已经扩散到 5% 的用户。
这是前端团队最经典的盲区:你的代码在成千上万个浏览器里跑,你对它的运行状态一无所知。 后端有日志、有 APM,挂了自己先知道;前端出事,永远是用户先发现、客服先来问。这篇文章不讲 Sentry 怎么注册账号,只讲生产级 React 应用的错误追踪怎么落地——SDK 初始化、Error Boundary、source map 上传、release 绑定、性能监控、隐私脱敏、Next.js 全家桶、告警规则。每一项都是"上线那天你后悔没做"的事。
一、为什么前端必须有错误追踪
先认清一个残酷事实:用户报上来的 bug,只是冰山一角。 大多数用户遇到报错时的真实行为不是反馈,而是:刷新一下,好了就继续用,没好就默默关掉,转去用竞品。有数据统计,遇到严重错误的用户里,真正会主动反馈的不到 5%。也就是说,你靠"用户反馈"驱动的 bug 发现机制,漏掉了 95% 的问题。
更糟的是前端错误的"沉默性"。后端 500 错误,监控立刻告警;前端一个 undefined is not a function,用户看到白屏,但你这边一片寂静——除非你主动去抓。这就是错误追踪(Error Tracking)存在的根本理由:把"出事靠用户报告"变成"出事系统先知道",最好在用户感知之前就抓到、修掉。
一个成熟的前端错误追踪系统要回答四个问题:
- 出了什么错:错误类型、调用栈、错误信息。
- 谁遇到的:用户环境(浏览器、版本、设备)、操作路径(点过哪些页面、调过哪些接口)。
- 在哪儿出的:精确到源码的哪一行——不是压缩后的
chunk-a3f.js:1:28471。 - 什么时候引入的:哪个版本、哪次发版带来的,方便回滚或定位 commit。
这四问,靠手搓日志几乎答不全。用户浏览器你登不上去、调用栈是压缩后的、版本信息没人记。Sentry 这类工具存在的意义,就是把这四件事打包解决。本文以 Sentry 为例(同类还有 Bugsnag、Rollbar、Glitchtip,思路一致),讲怎么把它接到位。
二、Sentry.init:DSN 与采样率,先接上再说
接入第一步是把 SDK 装上、初始化。最朴素的形式——传一个 DSN(Data Source Name,Sentry 项目的上报地址),SDK 就开始自动抓全局未捕获异常了。
pnpm add @sentry/react
// src/lib/sentry.ts
import * as Sentry from "@sentry/react";
export function initSentry() {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// 关键:环境标识,让你在 Sentry 后台区分 prod / staging / dev
environment: process.env.NODE_ENV,
// 发布版本号,绑 release 追踪(见第五节)
release: process.env.NEXT_PUBLIC_SENTRY_RELEASE,
// 采样率,见下文解释
tracesSampleRate: 0.2,
// 默认就是 1.0,错误事件 100% 上报;高 QPS 站点才需要降
sampleRate: 1.0,
// 关掉开发环境的自动上报,别污染数据
enabled: process.env.NODE_ENV === "production",
});
}
几个关键参数必须想清楚,不能抄个默认值就走:
DSN 是项目唯一标识,公开不算泄漏。 DSN 是上报地址,不是密钥。前端 SDK 必然把 DSN 打进客户端代码,所以 Sentry 服务端靠它做路由,不做鉴权。要控制"谁能往这个项目发数据",靠 Sentry 后台的入站过滤(限域名、限 IP),而不是藏 DSN。这点很多人搞反,以为 DSN 泄漏了天就塌了——其实塌的是你没做配额保护,被人刷数据把额度吃光。
tracesSampleRate 是性能数据的采样率,不是错误采样率。 这是新手最容易踩的坑。Sentry 里"事务(transaction/trace)"和"错误(error event)"是两回事:错误默认 100% 上报,sampleRate 控制它;事务(用于性能监控、Web Vitals 采集)才由 tracesSampleRate 控制,因为一次页面加载会产生大量 span,全量上报既贵又没必要。一个站日均 PV 几十万,tracesSampleRate 设 1.0,账单会让你怀疑人生;设 0.1~0.2,既保留统计意义,又把成本控住。
environment 必须显式传。 不传的话,本地开发、CI、预发、线上的错误全混在一起,后台没法按环境筛。我见过团队把本地的一个 bug 疯狂上报,一天刷出十万条事件,把真正的线上问题淹没——就是没分环境。
错误事件本身可以传函数做更精细的采样决策:
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampler: (samplingContext) => {
// 关键页面全量采样,边缘页面低采样
if (samplingContext.transactionName?.includes("/checkout")) {
return 1.0; // 支付链路,一条都不漏
}
if (samplingContext.transactionName?.includes("/api/")) {
return 0; // 接口调用不单独采样
}
return 0.1; // 其余 10%
},
});
原则:核心链路全抓,边缘页面采样。 用函数比用一个固定数字聪明得多。
三、Error Boundary:React 渲染错误的最后一道防线
Sentry.init 装上之后,全局的同步异常、未捕获的 Promise rejection、window.onerror,SDK 都会自动接住。但 React 的渲染错误是个例外——React 组件树渲染阶段抛出的错误,会冒泡到最近的 Error Boundary,不冒泡到 window.onerror。 也就是说,一个组件渲染炸了,你没装 Error Boundary,SDK 一个字都收不到,用户看到的就是整个子树白屏或整页卸载。
所以光 init 不够,必须用 Error Boundary 把渲染错误也喂给 Sentry。@sentry/react 自带了一个包装好的组件,但更常见的是自己写一个,因为你要给用户渲染一个兜底 UI:
// src/components/ErrorBoundary.tsx
import * as React from "react";
import * as Sentry from "@sentry/react";
interface Props {
children: React.ReactNode;
fallback: (error: Error, reset: () => void) => React.ReactNode;
/** 区分页面级 / 区块级,便于后台聚合 */
scope?: string;
}
interface State {
error: Error | null;
}
export class ErrorBoundary extends React.Component<Props, State> {
state: State = { error: null };
static getDerivedStateFromError(error: Error): State {
// 更新 state,触发 fallback UI 渲染
return { error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
// 这里把错误连同 React 组件栈一起上报
Sentry.withScope((scope) => {
scope.setTag("error_boundary", this.props.scope ?? "unknown");
// componentStack 是 Error Boundary 独有的金子,比普通调用栈更能定位组件
scope.setContext("react", { componentStack: errorInfo.componentStack });
Sentry.captureException(error);
});
}
reset = () => this.setState({ error: null });
render() {
if (this.state.error) {
return this.props.fallback(this.state.error, this.reset);
}
return this.props.children;
}
}
使用时,按"页面级 + 关键区块级"两层包:
// 页面最外层兜底:整个页面炸了也有个说法
<ErrorBoundary
scope="page"
fallback={(error, reset) => (
<CrashScreen error={error} onRetry={reset} />
)}
>
<App />
{/* 局部兜底:某个挂件炸了,不影响整页 */}
<ErrorBoundary
scope="recommendation-widget"
fallback={() => <div>推荐暂时不可用</div>}
>
<RecommendationWidget />
</ErrorBoundary>
</ErrorBoundary>
这里有两条实战经验。第一,Error Boundary 要分层,别整个应用只包一层。 整页只包一个,那一个小组件炸了就整页白屏,用户连导航栏都用不了。把侧边栏、推荐位、评论区这些"非关键模块"各自包一层,炸了降级显示,主流程还能用。第二,componentStack 是 React 错误追踪的金子。 普通调用栈告诉你"哪个函数报错",componentStack 告诉你"从根组件到出错组件的整条组件路径",定位问题快一个量级——别忘了把它塞进 scope。
四、source map 上传:把压缩后的报错映射回源码行
接好 SDK、包好 Error Boundary,你开始收到错误事件,点开一看——调用栈是这样的:
at e.exports (chunk-A3F9B2.js:1:28471)
at t.render (chunk-A3F9B2.js:1:18903)
at Object.Lc (framework-7c1.js:22:91023)
完全没法看。 生产环境代码是压缩过的,变量名被改成了 e、t、Lc,行号挤在第一行。你想知道是哪个文件的哪一行报的,根本对不上。这就是 source map 要解决的问题。
source map 是构建时生成的"压缩后代码 → 源码"的映射文件(.js.map)。原理是:浏览器里跑的还是压缩代码,但你把 source map 上传到 Sentry(注意是上传到 Sentry 服务器,不是随压缩代码一起部署到 CDN——那等于把源码公开了)。Sentry 收到错误事件后,用对应的 source map 反解,后台展示的就是原始的 src/components/Payment.tsx:42。
第一步:构建时开启 source map。 各打包器配置略不同,关键是只生成、不内联、生产环境不部署到 CDN。
// next.config.ts(节选,@sentry/nextjs 会接管,详见第八节)
// 如果是纯 Vite/Webpack 项目,思路一致:build 时输出 .map 文件
const config = {
// 关键:让打包器产出 source map
productionBrowserSourceMaps: true, // Next.js
// Vite: build.sourcemap = 'hidden'(hidden 意味着不在 bundle 里引用 .map,
// 也就是浏览器拿不到 map,但磁盘上有,给上传用)
};
hidden 这个值很关键:它产出 .map 文件,但不在 JS 文件末尾写 //# sourceMappingURL= 注释。这样浏览器不会去下载 map(用户看不到源码),磁盘上却有 map 供上传用。
第二步:上传 source map 到 Sentry。 用 @sentry/cli 或打包器插件:
# 用 CLI 手动上传,适合脚本化
SENTRY_AUTH_TOKEN=xxx sentry-cli sourcemaps upload \
--org my-org \
--project my-frontend \
--release $(git rev-parse HEAD) \
./dist
更工程化的做法是在构建流程里自动上传。CI 打包后立刻上传,绑定到当前 release:
# .github/workflows/deploy.yml 节选
- name: Build
run: pnpm build # 产出带 source map 的产物
- name: Upload sourcemaps to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
sentry-cli sourcemaps upload \
--release ${{ github.sha }} \
--url-prefix "~/static" \ # 关键:告诉 Sentry 这些文件在生产环境的 URL 前缀
./out/_next/static
- name: Deploy
run: ./deploy.sh # 然后再部署
--url-prefix 是这里最容易翻车的点。 Sentry 收到错误时,从调用栈里拿到的是"出错的脚本 URL",比如 https://cdn.app.com/_next/static/chunks/A3F9B2.js。它需要用这个 URL 去匹配你上传的 source map。--url-prefix "~/static" 的意思是"我上传的这些 map 对应线上 URL 的 ~/static/ 前缀"(~ 代表任意域名)。前缀对不上,source map 就是"上传了但匹配不上",调用栈还是一团乱码。上传完务必去后台随便点一个错误,确认调用栈已经变成源码路径,这一步才算通了。
另一个坑:source map 别进 git、别发到 CDN。 进 git 会让仓库膨胀,发到 CDN 等于公开你的源码。正确做法是 CI 里构建完就上传到 Sentry,然后构建产物里清掉 .map 文件再部署。
五、release 追踪:定位"哪个版本引入的 bug"
线上突然涌入一堆新错误,你的第一个问题永远是:这是哪个版本带来的? 如果没有 release 追踪,你只能看时间——"昨天发的版本,那八成是它",但拿不准。接上 release 追踪后,Sentry 后台会直接告诉你"这个错误首次出现在 v2.4.1,占比 100%,v2.4.0 没有"——一句话锁定凶手。
做法是把"版本号"贯穿整个链路:
// 1. 定义 release 标识(推荐用 commit SHA,绝对唯一)
// CI 里注入:export NEXT_PUBLIC_SENTRY_RELEASE=$(git rev-parse HEAD)
// 2. Sentry.init 里带上它
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_SENTRY_RELEASE, // 第四节已提
});
// 3. source map 上传时绑定同一个 release(第四节 CLI 命令里的 --release)
// 4. (可选)发版后创建一次 commit,让 Sentry 知道这次发布
# 发版后,告诉 Sentry "这个 release 部署了"
sentry-cli releases new $RELEASE
sentry-cli releases set-commits $RELEASE --auto # 关联对应的 git commit
sentry-cli releases finalize $RELEASE
接通后,Sentry 后台会多出几个值钱的视图:"首次出现的错误"(New Issues)——这是发版质量的核心指标,每次发版新增了多少错误一目了然;release 维度的错误趋势——某个 release 后错误量激增,立刻就能回滚决策。没有 release 追踪,你的发版质量是"靠用户反馈有没有炸"来感知的;有了它,发版后 10 分钟就能在后台看到这次发布的健康度。 这是现代前端 CI 的标配,不是可选项。
六、性能监控:Web Vitals 不是后台才需要
错误追踪只解决"崩没崩"的问题,但"慢"也是一种 bug——而且更隐蔽。用户不会告诉你"你这页加载慢了 800ms",他只会觉得"这站越来越难用"然后离开。Sentry 的性能监控(Performance)自动采集浏览器侧的 Web Vitals,正好补上这块。
接上后(tracesSampleRate > 0),SDK 会自动采集 Core Web Vitals,无需手动埋点:
- LCP(Largest Contentful Paint):最大内容绘制时间,衡量"用户看到主要内容要多久"。
- INP(Interaction to Next Paint):交互响应延迟,取代了老掉的 FID,是现在衡量"交互顺不顺"的核心。
- CLS(Cumulative Layout Shift):累积布局偏移,衡量"页面会不会乱跳"。
除了这些,还会采集页面加载的瀑布分解:DNS、TCP、TLS、TTFB、DOM 解析、脚本执行各占多少。这一层最有价值——你不用再凭感觉猜"慢在哪",后台直接给你慢请求的火焰图。 一个页面 p75 的 LCP 是 3.2 秒,点进去看到"等 API 响应占了 2.1 秒",问题立刻聚焦。
如果你想手动追踪某个具体业务流程(比如"从加购物车到结账完成"),可以自己起一个事务:
import * as Sentry from "@sentry/react";
async function checkout(cartId: string) {
// 手动起一个性能事务,追踪结账全流程
const transaction = Sentry.startTransaction({
name: "checkout-flow",
op: "checkout",
});
Sentry.getCurrentHub().configureScope((scope) => scope.setSpan(transaction));
try {
const span1 = transaction.startChild({ op: "validate", description: "校验库存" });
await validateStock(cartId);
span1.finish();
const span2 = transaction.startChild({ op: "pay", description: "调用支付" });
await pay(cartId);
span2.finish();
} finally {
transaction.finish(); // 不调 finish 的事务不会上报,这是常见坑
}
}
原则:自动 Web Vitals 先用起来,够覆盖大部分"页面慢"的问题;只有特定业务链路才手动起事务,别把每个函数都包成 span——那是噪音,不是数据。
七、隐私脱敏:beforeSend 把 PII 挡在门外
这一节是合规和安全的硬要求,不是可选项。Sentry 默认会上报很多上下文:用户信息、请求 URL、面包屑(用户点过啥、调过啥接口)、甚至某些情况下会把 DOM 元素序列化进去。这些里头很容易夹带 PII(个人身份信息)——邮箱、手机号、身份证、token、订单号。一旦 PII 进了 Sentry,就是一次合规事故。 GDPR、个保法都不饶人。
兜底的脱敏入口是 beforeSend hook——每个事件上报前都会经过它,你在里面改、删、丢都行:
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
beforeSend(event) {
return scrubEvent(event);
},
});
const PII_PATTERNS: Array<[RegExp, string]> = [
[/1[3-9]\d{9}/g, "[PHONE]"], // 手机号
[/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, "[EMAIL]"], // 邮箱
[/\b\d{15}(\d{2}[\dXx])?\b/g, "[ID]"], // 身份证
[/"(?:token|password|secret|apiKey)"\s*:\s*"[^"]*"/gi, '"$1":"[REDACTED]"'],
];
function scrubEvent(event: Sentry.Event): Sentry.Event | null {
// 1. 清请求 URL 里的 query 参数(最容易带 token)
if (event.request?.url) {
event.request.url = stripQueryParams(event.request.url);
}
// 2. 清面包屑里的 PII(接口 URL、消息文本)
event.breadcrumbs = event.breadcrumbs?.map((b) => ({
...b,
data: b.data && scrubObject(b.data),
}));
// 3. 清错误对象里的额外字段
if (event.exception?.values) {
for (const ex of event.exception.values) {
ex.stacktrace?.frames?.forEach((f) => {
if (f.vars) f.vars = scrubObject(f.vars);
});
}
}
// 4. 用户主动设置的身份信息(如非必要别上报精确身份)
if (event.user) {
event.user.email = event.user.email ? "[EMAIL]" : undefined;
event.user.ip_address = "[REDACTED]";
// 保留 user.id(内部 ID)做用户聚合够用,但别带真实身份
}
return event;
}
function scrubObject(obj: Record<string, unknown>): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(obj)) {
if (/token|password|secret|authorization/i.test(key)) {
out[key] = "[REDACTED]";
continue;
}
if (typeof value === "string") {
out[key] = PII_PATTERNS.reduce((s, [re, tag]) => s.replace(re, tag), value);
} else {
out[key] = value;
}
}
return out;
}
function stripQueryParams(url: string): string {
try {
const u = new URL(url);
u.searchParams.forEach((_, key) => {
if (/token|auth|key|email|phone/i.test(key)) {
u.searchParams.set(key, "[REDACTED]");
}
});
return u.toString();
} catch {
return url;
}
}
三条原则,按重要性排:
第一,白名单优于黑名单。 与其列"哪些字段是 PII 要脱敏",不如列"哪些字段允许上报"。但现实里 beforeSend 做黑名单覆盖更快落地——所以黑名单要写厚一点,定期 review。第二,token 别出现在 URL 里。 历史上无数事故都是因为 ?access_token=xxx 被打进了日志或监控。脱敏再严,不如从一开始就把鉴权放 header,URL 不带敏感参数。第三,用户身份用内部 ID,不上报邮箱/手机号。 Sentry 的 user feedback 功能需要邮箱,那是另一回事;默认错误事件里只塞 user.id(你系统里的内部 UUID),够做"这个用户报了多少次错"的聚合,又不会泄漏身份。
最后,如果某类事件压根不该上报(比如测试环境的噪音、已知的第三方脚本错误),beforeSend 里直接 return null 就能丢弃。它既能脱敏,也是最后一道过滤器。
八、Next.js 全家桶:@sentry/nextjs 一次配齐 server + client + edge
前面讲的是纯 React SPA 的接法。Next.js 应用是混合的——有客户端代码(浏览器跑的组件)、有服务端代码(API Route、Server Action、Server Components)、还有 Edge Runtime(中间件)。三套运行时三种上下文,手动分别接 Sentry 会很乱。@sentry/nextjs 就是为这个设计的,一个包搞定三个运行时。
pnpm add @sentry/nextjs
# 官方向导自动生成配置文件、改 next.config
npx @sentry/wizard@latest -i nextjs
向导会生成三个配置文件,对应三种运行时,各自调 Sentry.init:
// sentry.client.config.ts —— 浏览器端
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.2,
replaysSessionSampleRate: 0.1, // 会话回放:常态 10% 采样
replaysOnErrorSampleRate: 1.0, // 出错的会话 100% 回放
});
// sentry.server.config.ts —— Node 服务端(API Route / Server Action)
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.2,
});
// sentry.edge.config.ts —— Edge Runtime(中间件)
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.2,
});
这三个文件不是 Next.js 自动加载的,需要在 instrumentation.ts 里手动引入(这是 Next.js 的约定入口,启动时执行一次):
// instrumentation.ts —— 项目根目录
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("./sentry.edge.config");
}
// client 配置由 Next.js 自动注入,不在这里管
}
然后是 next.config.ts 用 withSentryConfig 包一层,source map 上传也在这里配,不用单独跑 CLI:
// next.config.ts
import { withSentryConfig } from "@sentry/nextjs";
const nextConfig = { /* 你的其它配置 */ };
export default withSentryConfig(nextConfig, {
// 自动上传 source map(构建时,依赖 SENTRY_AUTH_TOKEN 环境变量)
sourcemaps: {
deleteAfterCompile: true, // 上传完从构建产物里删掉,不进 CDN
},
// release 标识
release: { name: process.env.NEXT_PUBLIC_SENTRY_RELEASE },
// 关掉发版时往 Sentry 提交 commit(避免 CI 权限问题)
disableClientWebpackPlugin: false,
// 隐藏 console.log 之外的 org/project 默认配置
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
});
配完这套,一次 pnpm build 就同时完成三件事:构建三个运行时的产物、自动上传 source map、绑定 release。这是 Next.js 项目接 Sentry 的正确姿势:别手搓三个 SDK,让 @sentry/nextjs 帮你统一管运行时分发和 source map。 顺便说一句,会话回放(Session Replay)是个被低估的功能——出错的会话会录下用户操作,你能在后台像看视频一样回放"用户到底点了什么导致白屏",定位复现条件简直是降维打击。
九、告警规则:新错误第一时间推到 Slack
接好前面八节,错误事件会安静地躺在 Sentry 后台等你去看。但没人会盯着后台过日子。告警才是错误追踪的出口——否则数据再全,也是事后诸葛亮。 Sentry 的告警(Alerts / Rules)核心思想是:只对"新东西"和"异常增长"告警,不对"已知存量"告警。
最有价值的告警规则是"新错误":
规则:当某个项目出现「首次报告的错误」(New Issue)时,推送到 Slack 的
#frontend-alerts频道。
这一条覆盖了最重要的场景——发版后线上新增的错误立刻有人知道。Sentry 后台用 Issue 这个概念做去重:同样的调用栈被聚合成一个 Issue,1000 个用户报同一个错只算一个 Issue。所以"新 Issue"就是"没见过的错误",而不是"又有人报了一遍"。后者会告警疲劳,前者不会。
进阶一点,加几条更细的规则:
- 错误激增:某个 Issue 在 1 小时内事件数比平时高 5 倍 → 告警(说明突然恶化)。
- 高影响错误:某个 Issue 在 1 小时内影响了超过 100 个用户 → 告警(影响面大)。
- 支付/结账链路任何错误:tag 匹配
error_boundary:payment或 transaction 匹配/checkout→ 立刻告警(核心链路零容忍)。
// 配合代码里打 tag,让告警规则能精确匹配
Sentry.withScope((scope) => {
scope.setTag("module", "payment");
scope.setTag("severity", "revenue-impacting");
Sentry.captureException(err);
});
告警通道除了 Slack,还有邮件、PagerDuty(用于 P0 叫人)、企业微信、钉钉。一个建议的分工:普通新错误进 Slack 频道(团队可见、不强制响应);核心链路错误进 PagerDuty(强制值班响应)。 别把所有错误都进 PagerDuty,否则告警疲劳一到,P0 也没人看——这是上一篇文章讲过的教训。
最后一条经验:告警要带"够用的上下文,但别带全文"。 一条好的告警应该是:标题是错误摘要、附带影响用户数、首次出现时间、release 版本、Sentry 后台跳转链接。点链接进去看详情,而不是在 Slack 里塞一整个调用栈——没人会在聊天框里读 50 行 stack trace。Sentry 的 Slack 集成默认就长这样,别去改它。
小结
- 前端错误追踪的根本价值是变被动为主动——95% 的用户不会报 bug,等用户反馈就是放弃 95% 的可见度。接好错误追踪,让你在用户感知前就抓到问题。
Sentry.init的参数要想清楚:tracesSampleRate控的是性能事务不是错误(错误默认 100%),environment必须显式传,核心链路用tracesSampler函数做差异化采样。- Error Boundary 是 React 渲染错误的唯一接住点,不装它,SDK 收不到渲染错误。要分层包,别整页只包一层;
componentStack是定位组件的金子,别忘了上报。 - source map 是"看得懂调用栈"的前提——构建产出 map、上传到 Sentry、用
--url-prefix对齐线上 URL,三步缺一不可。map 别进 git、别上 CDN。 - release 追踪让"哪个版本引入的"一句话答清,发版健康度 10 分钟内可见;性能监控自动采集 Web Vitals,慢在哪段瀑布图说话。
- 隐私脱敏用
beforeSend兜底,黑名单写厚一点,token 别进 URL,用户身份用内部 ID 而非邮箱手机号。Next.js 项目用@sentry/nextjs统一管三个运行时,别手搓。 - 告警只对"新错误"和"异常增长"响,核心链路进 PagerDuty,普通新错误进 Slack,告警带摘要和跳转链接,别塞全文 stack。
一句话收尾:错误追踪不是"出事之后的工具",是"让你提前知道要出事"的系统。 用户遇到白屏那一刻,你的团队应该是从 Slack 告警里知道这件事的,而不是从客服工单里——这一前一后的差距,就是一个团队前端工程化成熟度的差距。投资这套东西的回报,和后端监控一样,从来不在风平浪静的日子,而在下一次半夜炸网的时候。
Related