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