前端监控与错误追踪

What — 是什么

前端监控是通过采集页面性能、错误、用户行为等数据,实现线上问题感知、定位和修复的系统性工程。

核心分类:

  • 异常监控:JS 运行时错误、Promise 异常、资源加载失败、接口错误
  • 性能监控:Web Vitals(LCP/FID/CLS/INP)、首屏时间、接口耗时
  • 行为监控:PV/UV、用户点击路径、页面停留时长
  • 录屏回放:通过 DOM 快照还原用户操作现场

关键特性:

  • 监控的核心价值:发现问题 → 定位问题 → 修复问题
  • SourceMap 上传到监控平台,线上代码也能定位到源码行
  • 采样率控制成本,不必 100% 采集

Why — 为什么

适用场景:

  • 所有线上项目(没有监控 = 盲飞)
  • 线上白屏/报错用户反馈时快速定位
  • 性能劣化趋势预警
  • 用户行为分析辅助产品决策

对比方案:

维度Sentry自建监控Google AnalyticsLogRocket
错误追踪极好自定义基础
性能监控自定义
录屏回放支持(付费)需自研不支持核心功能
SourceMap自动还原需自研不支持支持
成本免费额度 + 付费服务器成本免费付费

优缺点:

  • ✅ 优点:
    • 线上问题从”用户说”变为”数据说”
    • 错误聚合去重,避免告警风暴
    • SourceMap 还原源码位置
  • ❌ 缺点:
    • SDK 本身有性能开销(需控制采样率)
    • 自建监控运维成本高
    • 隐私合规需注意(脱敏用户数据)

How — 怎么用

快速上手

Sentry 接入:

// sentry.ts
import * as Sentry from '@sentry/react';

Sentry.init({
    dsn: 'https://xxx@sentry.io/123',
    environment: import.meta.env.MODE, // development / production
    release: import.meta.env.VITE_COMMIT_SHA,
    tracesSampleRate: 0.1, // 性能采样 10%
    replaysSessionSampleRate: 0.01, // 录屏采样 1%
    replaysOnErrorSampleRate: 1.0, // 错误时 100% 录屏
    integrations: [
        Sentry.browserTracingIntegration(),
        Sentry.replayIntegration(),
    ],
    // 过滤无意义错误
    ignoreErrors: [
        'ResizeObserver loop limit exceeded',
        'NetworkError',
        /Non-Error promise rejection captured/,
    ],
    // 用户的请求头中脱敏
    beforeSend(event) {
        if (event.request?.headers) {
            delete event.request.headers.Authorization;
        }
        return event;
    },
});

React 错误边界:

import * as Sentry from '@sentry/react';

const SentryErrorBoundary = Sentry.ErrorBoundary;

function App() {
    return (
        <SentryErrorBoundary fallback={<ErrorFallback />} showDialog>
            <Router />
        </SentryErrorBoundary>
    );
}

代码示例

手动上报错误:

// 捕获异常
try {
    await riskyOperation();
} catch (error) {
    Sentry.captureException(error, {
        tags: { module: 'payment' },
        extra: { orderId: '12345', amount: 99.9 },
    });
}

// 捕获消息
Sentry.captureMessage('支付超时', 'warning');

// 添加用户上下文
Sentry.setUser({ id: user.id, email: user.email });
Sentry.setTag('vip', user.isVip ? 'yes' : 'no');
Sentry.setContext('order', { id: orderId, status: 'pending' });

// 添加面包屑(操作轨迹)
Sentry.addBreadcrumb({
    category: 'ui.click',
    message: '点击提交按钮',
    level: 'info',
});

全局错误捕获:

// JS 运行时错误
window.onerror = (message, source, lineno, colno, error) => {
    Sentry.captureException(error);
};

// Promise 未捕获异常
window.addEventListener('unhandledrejection', (event) => {
    Sentry.captureException(event.reason);
});

// 资源加载失败
window.addEventListener('error', (event) => {
    if (event.target?.src || event.target?.href) {
        Sentry.captureMessage(`资源加载失败: ${event.target.src || event.target.href}`, 'error');
    }
}, true); // 捕获阶段

// 接口错误(axios 拦截器)
axios.interceptors.response.use(null, (error) => {
    if (error.response?.status >= 500) {
        Sentry.captureException(error, {
            tags: { api: error.config?.url, status: error.response.status },
        });
    }
    return Promise.reject(error);
});

自建性能监控:

// 采集 Web Vitals
function reportWebVitals() {
    // LCP
    new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];
        report('LCP', lastEntry.startTime);
    }).observe({ type: 'largest-contentful-paint', buffered: true });

    // FID / INP
    new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
            report('INP', entry.duration);
        });
    }).observe({ type: 'event', buffered: true });

    // CLS
    let clsValue = 0;
    new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
            if (!entry.hadRecentInput) clsValue += entry.value;
        });
        report('CLS', clsValue);
    }).observe({ type: 'layout-shift', buffered: true });
}

function report(metric: string, value: number) {
    const url = '/api/monitor';
    if (navigator.sendBeacon) {
        navigator.sendBeacon(url, JSON.stringify({ metric, value, url: location.href }));
    } else {
        fetch(url, { method: 'POST', body: JSON.stringify({ metric, value }), keepalive: true });
    }
}

Vue 全局错误处理:

// main.ts
const app = createApp(App);

app.config.errorHandler = (error, instance, info) => {
    Sentry.captureException(error as Error, {
        tags: { component: instance?.$options?.name, info },
    });
};

app.config.warnHandler = (msg, instance, trace) => {
    // 开发环境警告也可上报
    if (import.meta.env.PROD) {
        Sentry.captureMessage(`Vue warn: ${msg}`, 'warning');
    }
};

SourceMap 构建 & 上传:

// vite.config.ts
import { sentryVitePlugin } from '@sentry/vite-plugin';

export default defineConfig({
    build: {
        sourcemap: true, // 生成 SourceMap
    },
    plugins: [
        sentryVitePlugin({
            org: 'my-org',
            project: 'my-project',
            authToken: process.env.SENTRY_AUTH_TOKEN,
            release: process.env.VITE_COMMIT_SHA,
            // 构建后删除 SourceMap 文件,不部署到 CDN
            sourcemaps: { filesToDeleteAfterUpload: ['dist/**/*.map'] },
        }),
    ],
});

常见问题与踩坑

问题原因解决方案
线上代码无法定位行号SourceMap 未上传或未配置构建时上传 SourceMap 到 Sentry,不在 CDN 暴露
告警风暴同一错误大量触发Sentry 内置聚合 + 采样率 + ignoreErrors
SDK 影响性能上报请求过多批量上报 + 采样率 + sendBeacon
用户数据泄露上报包含敏感信息beforeSend 脱敏处理
跨域脚本无堆栈跨域 JS 无 SourceMap<script crossorigin> + CDN 配置 CORS

最佳实践

  • 错误监控 100% 采集,性能/录屏采样降低成本
  • SourceMap 构建时上传到监控平台,不部署到 CDN
  • beforeSend 脱敏用户数据,避免隐私泄露
  • 告警按模块/级别分组,配置通知规则避免噪音
  • 上报用 sendBeacon 确保页面关闭时不丢失

面试题

Q1: 前端有哪些错误类型?如何捕获?

四种主要类型:JS 运行时错误(window.onerror/try-catch);Promise 未捕获异常(unhandledrejection 事件);资源加载失败(捕获阶段的 error 事件,检查 event.target);接口错误(axios 拦截器捕获 HTTP 状态码异常)。

Q2: SourceMap 的原理是什么?线上如何安全使用?

SourceMap 是构建产物与源码的映射文件,记录压缩/编译后代码位置到源码位置的对应关系。线上安全做法:构建时生成 SourceMap 并上传到监控平台(如 Sentry),上传后从构建产物中删除 .map 文件,不部署到 CDN,避免源码泄露。

Q3: 前端错误上报有哪些方式?各有什么优缺点?

三种主流方式:navigator.sendBeacon(异步发送,页面关闭时不丢失,但有数据大小限制);fetch + keepalive(类似 Beacon,支持更大数据量);Image 打点(兼容性最好,仅支持 GET,数据量极有限)。推荐优先用 sendBeacon

Q4: 采样率如何设计?

错误监控 100% 采集(错误量小且价值高);性能监控按比例采样(如 10%~20%),根据流量调整;录屏回放低采样率(1%),错误触发时 100% 录屏。核心原则:高价值数据全量采集,成本高的数据按需采样,通过环境区分(生产采样,开发全量)。


相关链接: