💥 半夜3点被拉群骂?学会Sentry监控后,现在都是后端背锅了

前端线上问题怎么排查?Sentry + 埋点监控完整实战指南

本文详细介绍前端线上问题的排查思路,重点讲解 Sentry 错误监控和埋点系统的实战应用。

🎯 前言

作为前端开发,你是否遇到过这些场景:

  • 用户反馈页面白屏,但本地无法复现?
  • 线上某个功能突然报错,不知道影响了多少用户?
  • 性能指标下降,但不清楚具体是哪里出了问题?

这些问题的解决,都离不开一套完善的前端监控体系。本文将从实战角度,带你搭建完整的监控和问题排查方案。


📊 一、前端监控体系架构

完整的前端监控体系包括三大核心:

javascript 复制代码
┌─────────────────────────────────────┐
│         前端监控体系                  │
├─────────────────────────────────────┤
│  1. 错误监控(Sentry)               │
│     - JS 运行时错误                  │
│     - Promise 异常                   │
│     - 资源加载失败                   │
│     - 接口请求错误                   │
│                                     │
│  2. 性能监控                         │
│     - Core Web Vitals               │
│     - 页面加载性能                   │
│     - 接口性能                       │
│                                     │
│  3. 用户行为埋点                     │
│     - 页面访问(PV/UV)              │
│     - 用户点击行为                   │
│     - 业务流程追踪                   │
└─────────────────────────────────────┘

🔥 二、Sentry 错误监控实战

1. Sentry 初始化配置

javascript 复制代码
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV, // 区分环境
  release: `my-app@${process.env.RELEASE_VERSION}`, // 版本号
  
  // 集成性能监控和会话重放
  integrations: [
    new BrowserTracing({
      tracePropagationTargets: ['api.yourapp.com'], // 追踪的 API 域名
    }),
    new Sentry.Replay({
      maskAllText: false, // 是否遮罩文本
      blockAllMedia: false, // 是否阻止媒体
    }),
  ],
  
  // 采样率配置(控制成本)
  tracesSampleRate: 0.1, // 性能监控 10% 采样
  replaysSessionSampleRate: 0.1, // 正常会话 10% 录制
  replaysOnErrorSampleRate: 1.0, // 错误会话 100% 录制
  
  // 过滤敏感信息
  beforeSend(event, hint) {
    // 移除敏感数据
    if (event.request) {
      delete event.request.cookies;
      delete event.request.headers?.Authorization;
    }
    
    // 过滤本地开发环境
    if (window.location.hostname === 'localhost') {
      return null;
    }
    
    return event;
  },
  
  // 忽略已知的无害错误
  ignoreErrors: [
    'ResizeObserver loop limit exceeded',
    'Non-Error promise rejection captured',
    'Network request failed', // 网络问题
  ],
});

2. 设置用户上下文信息

javascript 复制代码
// 用户登录后设置用户信息
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.name,
  // 自定义字段
  vipLevel: user.vipLevel,
});

// 设置标签(用于过滤和分组)
Sentry.setTag('page', 'checkout');
Sentry.setTag('feature', 'payment');
Sentry.setTag('app_version', '2.1.0');

// 设置额外上下文
Sentry.setContext('device', {
  brand: getBrand(),
  model: getModel(),
  os: getOS(),
  network: getNetworkType(), // 4G/5G/WiFi
});

// 记录用户行为面包屑
Sentry.addBreadcrumb({
  category: 'ui.click',
  message: '用户点击了提交订单按钮',
  level: 'info',
  data: {
    orderId: '123456',
    amount: 299.0,
  },
});

3. React/Vue 框架集成

React Error Boundary:

javascript 复制代码
import { ErrorBoundary } from '@sentry/react';

function App() {
  return (
    <ErrorBoundary
      fallback={({ error, componentStack, resetError }) => (
        <div>
          <h1>出错了!</h1>
          <button onClick={resetError}>重试</button>
        </div>
      )}
      showDialog={false} // 是否显示错误反馈对话框
    >
      <Routes />
    </ErrorBoundary>
  );
}

Vue 集成:

javascript 复制代码
import * as Sentry from '@sentry/vue';

const app = createApp(App);

Sentry.init({
  app,
  trackComponents: true, // 追踪组件性能
  hooks: ['mount', 'update'], // 追踪的生命周期
});

4. 手动捕获错误

javascript 复制代码
// 捕获异常
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'payment',
    },
    contexts: {
      order: {
        id: orderId,
        amount: amount,
      },
    },
  });
}

// 记录消息
Sentry.captureMessage('用户尝试访问已下架的商品', {
  level: 'warning',
  extra: {
    productId: 12345,
  },
});

5. SourceMap 配置(重要!)

javascript 复制代码
// vite.config.js
export default {
  build: {
    sourcemap: true, // 生成 sourcemap
  },
};

// 使用 Sentry CLI 上传 sourcemap
// package.json
{
  "scripts": {
    "build": "vite build && sentry-cli sourcemaps upload --org=your-org --project=your-project ./dist"
  }
}

// .sentryclirc 配置文件
[auth]
token=YOUR_AUTH_TOKEN

[defaults]
url=https://sentry.io/
org=your-org
project=your-project

生产环境删除 sourcemap:

javascript 复制代码
// 上传后删除,防止暴露源码
"build": "vite build && sentry-cli sourcemaps upload ./dist && rm ./dist/**/*.map"

📈 三、用户行为埋点系统

1. 埋点 SDK 设计

javascript 复制代码
class Tracker {
  constructor(config) {
    this.config = config; // { url: '上报地址', appId: '应用ID' }
    this.queue = []; // 事件队列
    this.timer = null;
    
    // 启动定时上报
    this.startTimer();
    
    // 页面卸载时上报
    window.addEventListener('beforeunload', () => this.flush());
  }

  // 获取公共参数
  getCommonParams() {
    return {
      app_id: this.config.appId,
      app_version: APP_VERSION,
      platform: 'web',
      user_id: this.getUserId(),
      session_id: this.getSessionId(),
      device_id: this.getDeviceId(),
      page_url: window.location.href,
      page_title: document.title,
      referrer: document.referrer,
      screen_resolution: `${window.screen.width}x${window.screen.height}`,
      viewport_size: `${window.innerWidth}x${window.innerHeight}`,
      user_agent: navigator.userAgent,
      network_type: this.getNetworkType(),
      timestamp: Date.now(),
    };
  }

  // 上报事件
  track(eventName, params = {}) {
    const event = {
      event_name: eventName,
      ...this.getCommonParams(),
      ...params,
    };
    
    this.queue.push(event);
    
    // 队列达到阈值立即上报
    if (this.queue.length >= 10) {
      this.flush();
    }
  }

  // 批量上报
  flush() {
    if (this.queue.length === 0) return;
    
    const events = [...this.queue];
    this.queue = [];
    
    // 使用 sendBeacon 保证页面卸载时也能发送
    const data = JSON.stringify(events);
    
    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.config.url, data);
    } else {
      fetch(this.config.url, {
        method: 'POST',
        body: data,
        headers: { 'Content-Type': 'application/json' },
        keepalive: true, // 页面卸载后仍继续请求
      }).catch(console.error);
    }
  }

  // 定时上报(每 5 秒)
  startTimer() {
    this.timer = setInterval(() => {
      this.flush();
    }, 5000);
  }

  // 获取设备 ID
  getDeviceId() {
    let deviceId = localStorage.getItem('device_id');
    if (!deviceId) {
      deviceId = `device_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      localStorage.setItem('device_id', deviceId);
    }
    return deviceId;
  }

  // 获取会话 ID
  getSessionId() {
    let sessionId = sessionStorage.getItem('session_id');
    if (!sessionId) {
      sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
      sessionStorage.setItem('session_id', sessionId);
    }
    return sessionId;
  }

  // 获取网络类型
  getNetworkType() {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    return connection?.effectiveType || 'unknown';
  }

  // 获取用户 ID
  getUserId() {
    return window.userInfo?.id || 'anonymous';
  }
}

// 初始化
const tracker = new Tracker({
  url: 'https://api.yourapp.com/track',
  appId: 'your-app-id',
});

export default tracker;

2. 常见埋点场景

页面访问埋点(PV/UV):

javascript 复制代码
// 路由变化时自动埋点
router.afterEach((to, from) => {
  tracker.track('page_view', {
    page_name: to.name,
    page_path: to.path,
    from_page: from.path,
  });
});

点击埋点:

javascript 复制代码
// Vue 指令方式
app.directive('track', {
  mounted(el, binding) {
    el.addEventListener('click', () => {
      tracker.track('click', {
        element_id: binding.value.id,
        element_name: binding.value.name,
        element_type: el.tagName.toLowerCase(),
      });
    });
  },
});

// 使用
<button v-track="{ id: 'submit_order', name: '提交订单' }">
  提交订单
</button>

业务埋点:

javascript 复制代码
// 下单成功
tracker.track('purchase_complete', {
  order_id: order.id,
  total_amount: order.totalAmount,
  product_count: order.products.length,
  payment_method: order.paymentMethod,
  coupon_used: order.couponId ? 'yes' : 'no',
});

// 搜索行为
tracker.track('search', {
  keyword: searchKeyword,
  result_count: results.length,
  search_type: 'product',
});

曝光埋点(使用 IntersectionObserver):

javascript 复制代码
class ExposureTracker {
  constructor(tracker) {
    this.tracker = tracker;
    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const { elementId, elementData } = entry.target.dataset;
            this.tracker.track('exposure', {
              element_id: elementId,
              ...JSON.parse(elementData || '{}'),
            });
            // 上报后停止观察
            this.observer.unobserve(entry.target);
          }
        });
      },
      { threshold: 0.5 } // 50% 可见时触发
    );
  }

  observe(element, data) {
    element.dataset.elementId = data.id;
    element.dataset.elementData = JSON.stringify(data);
    this.observer.observe(element);
  }
}

⚡ 四、性能监控

1. Core Web Vitals 监控

javascript 复制代码
import { onLCP, onFID, onCLS, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  tracker.track('web_vitals', {
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // good/needs-improvement/poor
    delta: metric.delta,
    id: metric.id,
  });
}

// 最大内容绘制
onLCP(sendToAnalytics);

// 首次输入延迟
onFID(sendToAnalytics);

// 累积布局偏移
onCLS(sendToAnalytics);

// 首次内容绘制
onFCP(sendToAnalytics);

// 首字节时间
onTTFB(sendToAnalytics);

2. 页面加载性能

javascript 复制代码
window.addEventListener('load', () => {
  // 使用 Performance API
  const perfData = performance.getEntriesByType('navigation')[0];
  
  tracker.track('page_performance', {
    // DNS 查询时间
    dns: perfData.domainLookupEnd - perfData.domainLookupStart,
    // TCP 连接时间
    tcp: perfData.connectEnd - perfData.connectStart,
    // 请求响应时间
    ttfb: perfData.responseStart - perfData.requestStart,
    // 内容下载时间
    download: perfData.responseEnd - perfData.responseStart,
    // DOM 解析时间
    domParse: perfData.domInteractive - perfData.responseEnd,
    // 资源加载时间
    resourceLoad: perfData.loadEventStart - perfData.domContentLoadedEventEnd,
    // 总加载时间
    total: perfData.loadEventEnd - perfData.fetchStart,
  });
});

3. 接口性能监控

javascript 复制代码
// 拦截 fetch
const originalFetch = window.fetch;
window.fetch = function (...args) {
  const startTime = performance.now();
  const url = args[0];
  const method = args[1]?.method || 'GET';
  
  return originalFetch.apply(this, args)
    .then((response) => {
      const duration = performance.now() - startTime;
      
      tracker.track('api_performance', {
        url: url,
        method: method,
        status: response.status,
        duration: Math.round(duration),
        success: response.ok,
      });
      
      return response;
    })
    .catch((error) => {
      const duration = performance.now() - startTime;
      
      tracker.track('api_error', {
        url: url,
        method: method,
        duration: Math.round(duration),
        error: error.message,
      });
      
      // 同时上报到 Sentry
      Sentry.captureException(error, {
        tags: { api: url },
      });
      
      throw error;
    });
};

🔍 五、线上问题排查实战流程

步骤 1:问题发现

监控告警渠道:

  • Sentry 邮件/钉钉告警
  • 性能监控平台告警
  • 用户客服反馈
  • 业务数据异常

步骤 2:信息收集(从 Sentry)

关键信息:

  1. 错误堆栈 - 定位具体代码位置(结合 SourceMap)
  2. 用户信息 - 影响的用户 ID、邮箱
  3. 设备环境 - 浏览器、操作系统、网络
  4. Session Replay - 回放用户操作录像
  5. Breadcrumbs - 错误前的操作轨迹
  6. 发生频率 - 错误发生次数、影响用户数

步骤 3:埋点数据分析

javascript 复制代码
// 通过埋点还原用户操作路径
1. 进入商品详情页 (14:30:25)
2. 点击"立即购买" (14:30:40)
3. 选择规格 (14:30:45)
4. 点击"提交订单" (14:30:50)
5. ❌ 错误发生 (14:30:51)

步骤 4:影响范围评估

多维度分析:

  • 时间维度 - 从什么时候开始出现?
  • 用户维度 - 影响多少用户?占比多少?
  • 版本维度 - 是否特定版本才有?
  • 设备维度 - 是否特定浏览器/OS?
  • 地域维度 - 是否特定地区?

步骤 5:问题复现

  1. 根据 Sentry 信息在本地构造场景
  2. 使用相同浏览器版本
  3. 查看 Session Replay 模拟操作
  4. 检查网络状态(Chrome DevTools 限速)

步骤 6:解决问题

快速止损:

  • 回滚到上一个稳定版本
  • 灰度关闭出问题的功能
  • 降级处理(禁用非核心功能)

根本解决:

  • 修复代码 bug
  • 增加容错逻辑
  • 补充单元测试

💡 六、实战案例分享

案例 1:白屏问题排查

现象: 部分移动端用户反馈页面白屏

Sentry 信息:

vbnet 复制代码
ChunkLoadError: Loading chunk 3 failed.
(error: https://cdn.example.com/static/js/3.abc123.js)

浏览器:Safari 14.0 / iOS 14.5
影响用户:235 人
错误率:2.3%

埋点数据:

  • 错误集中在路由切换时
  • 用户从首页点击"我的订单"后白屏

Session Replay:

  • 用户点击按钮 → 页面加载状态 → 白屏

原因分析: 路由懒加载的 chunk 文件加载失败(CDN 缓存问题)

解决方案:

javascript 复制代码
// 添加 chunk 加载失败重试机制
const loadWithRetry = (importFunc, retries = 3) => {
  return new Promise((resolve, reject) => {
    importFunc()
      .then(resolve)
      .catch((error) => {
        if (retries > 0 && error.name === 'ChunkLoadError') {
          console.log(`Chunk 加载失败,重试中... (剩余 ${retries} 次)`);
          // 延迟后重试
          setTimeout(() => {
            loadWithRetry(importFunc, retries - 1).then(resolve, reject);
          }, 1000);
        } else {
          reject(error);
        }
      });
  });
};

// 路由配置
const routes = [
  {
    path: '/orders',
    component: () => loadWithRetry(() => import('./views/Orders.vue')),
  },
];

案例 2:接口请求突然大量 500 错误

现象: 监控告警,某接口 5xx 错误率从 0.1% 升至 5%

Sentry 信息:

makefile 复制代码
POST /api/orders/create
Status: 500 Internal Server Error
影响用户:1,200+ 人
时间段:14:00-14:30

埋点分析:

  • 错误集中在"提交订单"步骤
  • 特定商品 ID: 12345 触发高频

排查过程:

  1. 联系后端查看日志 → 数据库查询超时
  2. 发现商品 12345 的库存数据异常(负数)
  3. 查询导致死锁

解决方案:

  • 前端:添加超时重试 + 降级提示
  • 后端:修复数据 + 增加熔断机制

🛡️ 七、最佳实践总结

1. Sentry 使用技巧

DO:

  • 设置合理的采样率(控制成本)
  • 上传 SourceMap 到 Sentry(生产删除)
  • 配置错误分组规则
  • 设置告警通知规则
  • 定期清理过期数据

DON'T:

  • 不要上报敏感信息(密码、token)
  • 不要把所有错误都上报(过滤无用错误)
  • 不要在生产环境保留 SourceMap 文件
  • 不要忘记设置 environment 和 release

2. 埋点设计原则

  • 准确性 - 埋点数据要准确反映用户行为
  • 完整性 - 覆盖关键业务流程
  • 性能 - 批量上报,避免阻塞主线程
  • 隐私 - 不采集敏感个人信息

3. 监控告警策略

javascript 复制代码
// 错误率阈值告警
错误率 > 1% → 发送告警
错误率 > 5% → 紧急告警 + 自动回滚

// 性能阈值告警
LCP > 2.5s → 警告
LCP > 4.0s → 严重

// 业务指标告警
下单转化率下降 > 20% → 告警

4. SourceMap 管理

bash 复制代码
# 构建时生成 sourcemap
npm run build

# 上传到 Sentry
sentry-cli sourcemaps upload --org=your-org --project=your-project ./dist

# 删除本地 sourcemap(防止泄露源码)
find ./dist -name "*.map" -delete

📚 八、推荐工具和资源

监控工具对比

工具 优势 适用场景
Sentry 开源、功能强大、支持多语言 中小型团队、个人项目
Fundebug 国内服务、中文支持好 国内项目
阿里云 ARMS 集成度高、适合阿里云生态 阿里云用户
腾讯云前端性能监控 性能监控强 腾讯云用户
自建方案 完全可控 大型团队、特殊需求

学习资源


🎉 总结

一套完善的前端监控体系包括:

  1. 错误监控(Sentry)- 及时发现问题
  2. 性能监控(Web Vitals)- 保证用户体验
  3. 用户行为埋点 - 还原问题场景
  4. 告警机制 - 快速响应
  5. 问题排查流程 - 高效解决

只有建立了完善的监控体系,才能在线上问题发生时,快速定位、快速解决,保障用户体验。


如果觉得这篇文章对你有帮助,欢迎点赞收藏!有任何问题欢迎在评论区讨论 💬

相关文章推荐:

#前端 #监控 #Sentry #性能优化 #前端工程化

相关推荐
Olrookie2 小时前
若依前后端分离版学习笔记(十九)——导入,导出实现流程及图片,文件组件
前端·vue.js·笔记
浩男孩3 小时前
🍀终于向常量组件下手了,使用TypeScript 基于 TDesign二次封装常量组件 🚀🚀
前端·vue.js
玲小珑3 小时前
LangChain.js 完全开发手册(十三)AI Agent 生态系统与工具集成
前端·langchain·ai编程
布列瑟农的星空3 小时前
什么?sessionStorage可以跨页签?
前端
苏打水com3 小时前
网易前端业务:内容生态与游戏场景下的「沉浸式体验」与「性能优化」实践
前端·游戏·性能优化
恋猫de小郭3 小时前
React 和 React Native 不再直接归属 Meta,React 基金会成立
android·前端·ios
掘金安东尼3 小时前
前端周刊434期(2025年9月29日–10月5日)
前端·javascript·面试
brzhang3 小时前
当我第一次看到 snapDOM,我想:这玩意儿终于能解决网页「截图」这破事了?
前端·后端·架构
掘金安东尼3 小时前
前端周刊433期(2025年9月22日–9月28日)
前端·javascript·github