Sentry 前端集成 #
前端集成概述 #
前端应用运行在用户的浏览器中,面临各种复杂的环境和设备。Sentry 前端集成可以帮助你:
text
┌─────────────────────────────────────────────────────────────┐
│ 前端监控价值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 捕获 JavaScript 错误 │
│ - 未捕获的异常 │
│ - Promise 拒绝 │
│ - 控制台错误 │
│ │
│ 2. 监控性能 │
│ - 页面加载时间 │
│ - API 请求耗时 │
│ - 前端资源加载 │
│ │
│ 3. 理解用户行为 │
│ - 用户操作路径 │
│ - 设备和浏览器信息 │
│ - Session Replay │
│ │
└─────────────────────────────────────────────────────────────┘
React 集成 #
安装 #
bash
# npm
npm install @sentry/react
# yarn
yarn add @sentry/react
基础配置 #
javascript
// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import * as Sentry from "@sentry/react";
import App from "./App";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: process.env.NODE_ENV,
release: `my-react-app@${process.env.npm_package_version}`,
});
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Error Boundary #
javascript
// ErrorBoundary.jsx
import * as Sentry from "@sentry/react";
import React from "react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
Sentry.withScope((scope) => {
scope.setExtras({
componentStack: errorInfo.componentStack,
});
Sentry.captureException(error);
});
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>Something went wrong.</h1>
<button onClick={() => window.location.reload()}>
Refresh Page
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
javascript
// 使用 Error Boundary
import ErrorBoundary from "./ErrorBoundary";
import App from "./App";
function Root() {
return (
<ErrorBoundary>
<App />
</ErrorBoundary>
);
}
使用 Sentry Error Boundary 组件 #
javascript
// 使用 Sentry 提供的 ErrorBoundary
import * as Sentry from "@sentry/react";
function App() {
return (
<Sentry.ErrorBoundary
fallback={({ error, componentStack, resetError }) => (
<div>
<h1>Something went wrong!</h1>
<p>{error.message}</p>
<button onClick={resetError}>Try again</button>
</div>
)}
onReset={() => {
// 重置应用状态
}}
beforeCapture={(scope, error, componentStack) => {
scope.setTag("location", window.location.pathname);
scope.setExtra("componentStack", componentStack);
}}
>
<MainApp />
</Sentry.ErrorBoundary>
);
}
React Router 集成 #
javascript
// src/index.js
import * as Sentry from "@sentry/react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
]);
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.reactRouterV6BrowserTracingIntegration({
useEffect: React.useEffect,
}),
],
tracesSampleRate: 0.1,
});
function App() {
return <RouterProvider router={router} />;
}
React 组件性能监控 #
javascript
import * as Sentry from "@sentry/react";
// 使用 Sentry Profiler 包裹组件
function App() {
return (
<Sentry.Profiler>
<MainContent />
</Sentry.Profiler>
);
}
// 测量特定组件的渲染性能
const ProfiledComponent = Sentry.withProfiler(ExpensiveComponent);
Vue 集成 #
安装 #
bash
# npm
npm install @sentry/vue
# yarn
yarn add @sentry/vue
Vue 3 配置 #
javascript
// src/main.js
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import * as Sentry from "@sentry/vue";
import App from "./App.vue";
const app = createApp(App);
const router = createRouter({
history: createWebHistory(),
routes: [
// 路由配置
],
});
Sentry.init({
app,
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration({
router,
}),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: import.meta.env.MODE,
release: `my-vue-app@${import.meta.env.VITE_APP_VERSION}`,
logErrors: true,
});
app.use(router);
app.mount("#app");
Vue 2 配置 #
javascript
// src/main.js
import Vue from "vue";
import Router from "vue-router";
import * as Sentry from "@sentry/vue";
import App from "./App.vue";
Vue.use(Router);
const router = new Router({
mode: "history",
routes: [
// 路由配置
],
});
Sentry.init({
Vue,
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
new Sentry.Integrations.VueRouter({
router,
}),
],
tracesSampleRate: 0.1,
logErrors: true,
});
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
Vue Error Handler #
javascript
// Vue 自动捕获组件错误
// 也可以手动添加错误处理
import * as Sentry from "@sentry/vue";
Vue.config.errorHandler = (error, vm, info) => {
Sentry.withScope((scope) => {
scope.setExtra("component", vm?.$options?.name);
scope.setExtra("info", info);
Sentry.captureException(error);
});
};
Vue 组件追踪 #
javascript
import * as Sentry from "@sentry/vue";
// 追踪组件生命周期
export default {
name: "TrackedComponent",
mounted() {
const transaction = Sentry.startTransaction({
name: "TrackedComponent.mounted",
op: "vue.component",
});
// 组件逻辑...
transaction.finish();
},
};
Angular 集成 #
安装 #
bash
# npm
npm install @sentry/angular
# yarn
yarn add @sentry/angular
基础配置 #
typescript
// src/app/app.module.ts
import { NgModule, ErrorHandler } from "@angular/core";
import { Router } from "@angular/router";
import * as Sentry from "@sentry/angular";
@NgModule({
// ...
providers: [
{
provide: ErrorHandler,
useValue: Sentry.createErrorHandler({
showDialog: false,
}),
},
{
provide: Sentry.TraceService,
deps: [Router],
},
{
provide: APP_INITIALIZER,
useFactory: () => () => {},
deps: [Sentry.TraceService],
multi: true,
},
],
// ...
})
export class AppModule {}
typescript
// src/main.ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import * as Sentry from "@sentry/angular";
import { AppModule } from "./app/app.module";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: "production",
release: `my-angular-app@1.0.0`,
});
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
Angular Error Handler #
typescript
// custom-error-handler.ts
import { ErrorHandler, Injectable } from "@angular/core";
import * as Sentry from "@sentry/angular";
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
handleError(error: any): void {
Sentry.captureException(error.originalError || error);
// 可选:显示错误对话框
// Sentry.showReportDialog({ eventId });
console.error(error);
}
}
Angular HTTP 拦截器 #
typescript
// sentry.interceptor.ts
import { Injectable } from "@angular/core";
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from "@angular/common/http";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import * as Sentry from "@sentry/angular";
@Injectable()
export class SentryInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
return next.handle(request).pipe(
tap({
error: (error) => {
Sentry.withScope((scope) => {
scope.setTag("http.request", request.url);
scope.setExtra("request", {
method: request.method,
url: request.url,
body: request.body,
});
Sentry.captureException(error);
});
},
})
);
}
}
Next.js 集成 #
安装 #
bash
# npm
npm install @sentry/nextjs
# yarn
yarn add @sentry/nextjs
自动配置 #
bash
# 运行向导自动配置
npx @sentry/wizard@latest -i nextjs
手动配置 #
javascript
// next.config.js
const { withSentryConfig } = require("@sentry/nextjs");
/** @type {import('next').NextConfig} */
const nextConfig = {
// Next.js 配置
};
const sentryWebpackPluginOptions = {
silent: true,
authToken: process.env.SENTRY_AUTH_TOKEN,
org: "my-org",
project: "my-project",
};
module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions);
javascript
// sentry.client.config.js
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: process.env.NODE_ENV,
});
javascript
// sentry.server.config.js
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
tracesSampleRate: 0.1,
environment: process.env.NODE_ENV,
});
javascript
// sentry.edge.config.js
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
tracesSampleRate: 0.1,
environment: process.env.NODE_ENV,
});
Next.js API 路由 #
javascript
// pages/api/users.js
import * as Sentry from "@sentry/nextjs";
export default async function handler(req, res) {
try {
const users = await fetchUsers();
res.status(200).json(users);
} catch (error) {
Sentry.captureException(error);
res.status(500).json({ error: "Failed to fetch users" });
}
}
Next.js App Router #
typescript
// app/layout.tsx
import * as Sentry from "@sentry/nextjs";
import { ErrorBoundary } from "./ErrorBoundary";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
{children}
</Sentry.ErrorBoundary>
</body>
</html>
);
}
typescript
// app/error.tsx
"use client";
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
export default function Error({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => window.location.reload()}>
Refresh Page
</button>
</div>
);
}
Session Replay #
启用 Session Replay #
javascript
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
配置选项 #
javascript
Sentry.init({
integrations: [
Sentry.replayIntegration({
// 遮罩所有文本
maskAllText: true,
// 阻止所有媒体
blockAllMedia: true,
// 自定义遮罩选择器
mask: [".sensitive-data", "[data-sentry-mask]"],
// 自定义阻止选择器
block: [".blocked-element", "[data-sentry-block]"],
// 忽略特定元素
ignore: [".ignore-element", "[data-sentry-ignore]"],
// 网络请求捕获
networkDetailAllowUrls: [/api\.example\.com/],
networkCaptureBodies: true,
// 回放质量
replaysSamplingRate: 1.0,
}),
],
});
隐私保护 #
javascript
// HTML 元素隐私配置
<div>
{/* 遮罩文本 */}
<span className="sensitive-data" data-sentry-mask>
Credit Card: 4111-1111-1111-1111
</span>
{/* 完全阻止 */}
<div data-sentry-block>
<img src="profile.jpg" alt="Profile" />
</div>
{/* 忽略(不录制) */}
<div data-sentry-ignore>
<canvas id="animation" />
</div>
</div>
性能监控 #
前端性能指标 #
javascript
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration({
// 追踪特定来源
traceFetch: true,
traceXHR: true,
// 排除特定请求
shouldCreateSpanForRequest: (url) => {
return !url.includes("/health");
},
}),
],
tracesSampleRate: 0.1,
});
自定义性能追踪 #
javascript
import * as Sentry from "@sentry/react";
// 追踪自定义操作
const transaction = Sentry.startTransaction({
name: "Load User Data",
op: "data.load",
});
const span1 = transaction.startChild({
op: "fetch",
description: "Fetch user profile",
});
const user = await fetchUserProfile();
span1.finish();
const span2 = transaction.startChild({
op: "process",
description: "Process user data",
});
processUserData(user);
span2.finish();
transaction.finish();
Web Vitals #
javascript
import * as Sentry from "@sentry/react";
// Sentry 自动捕获 Web Vitals
// - LCP (Largest Contentful Paint)
// - FID (First Input Delay)
// - CLS (Cumulative Layout Shift)
// - FCP (First Contentful Paint)
// - TTFB (Time to First Byte)
Sentry.init({
dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
integrations: [
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 0.1,
// 设置性能阈值
_experiments: {
// 记录长任务
recordLongTasks: true,
},
});
前端最佳实践 #
1. 环境区分 #
javascript
Sentry.init({
dsn: process.env.NODE_ENV === "production"
? "https://xxxxxxxx@sentry.io/123"
: undefined, // 开发环境不上报
environment: process.env.NODE_ENV,
});
2. 版本管理 #
javascript
Sentry.init({
release: `${process.env.npm_package_name}@${process.env.npm_package_version}`,
// 或者使用 Git commit hash
release: process.env.GIT_COMMIT_SHA,
});
3. 用户追踪 #
javascript
// 用户登录后设置
function onLogin(user) {
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
}
// 用户登出后清除
function onLogout() {
Sentry.setUser(null);
}
4. 过滤噪音 #
javascript
Sentry.init({
ignoreErrors: [
// 浏览器扩展错误
"Non-Error promise rejection captured",
/chrome-extension/,
/moz-extension/,
// 网络错误
"NetworkError",
"Failed to fetch",
"Network request failed",
// 用户行为导致
"ResizeObserver loop",
"Script error",
],
denyUrls: [
/extensions\//i,
/^chrome:\/\//i,
/google-analytics\.com/i,
],
});
5. Source Map 配置 #
javascript
// next.config.js
const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(nextConfig, {
silent: true,
authToken: process.env.SENTRY_AUTH_TOKEN,
org: "my-org",
project: "my-project",
// Source Map 配置
sourcemaps: {
assets: ["./public/**/*"],
ignore: ["./node_modules/**/*"],
},
});
下一步 #
现在你已经掌握了前端集成的知识,接下来学习 后端集成 了解如何在后端项目中集成 Sentry!
最后更新:2026-03-29