前端监控与错误上报(Sentry)

1. 后端视角理解前端监控

后端写日志你很熟:log.error(...) + ELK / Loki / Splunk / 阿里云 SLS。出了事去查日志就行。

前端没有"服务器"这么一说——代码跑在千万用户各自的浏览器里

没有监控 = 盲人摸象

Java 对照:类似 SkyWalking / Sentry / Arthas 的前端版——给每个用户的浏览器装一个"黑匣子",出事了能回放。

常见方案

in4vue 用 Sentry


2. Sentry 能回答什么

Sentry 接入后能看到:

核心 1 + 2 个功能就够 95% 场景。


3. 接入:最小可用版本

pnpm add @sentry/vue
// src/main.ts
import { createApp } from 'vue'
import * as Sentry from '@sentry/vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

if (import.meta.env.PROD) {
  Sentry.init({
    app,
    dsn: import.meta.env.VITE_SENTRY_DSN,
    environment: import.meta.env.MODE,
    release: import.meta.env.VITE_APP_VERSION, // 见后面的 Release 章节

    // 性能监控采样率(1.0 = 100%, 生产建议 0.1-0.3)
    tracesSampleRate: 0.1,
    integrations: [
      Sentry.browserTracingIntegration({ router }),
    ],

    // 忽略这些错误(浏览器插件 / 第三方脚本的噪音)
    ignoreErrors: [
      'ResizeObserver loop',
      'Non-Error promise rejection captured',
      /extension\/\//i,
    ],

    beforeSend(event, hint) {
      // 开发时本地不上报(双重保险)
      if (import.meta.env.DEV) return null
      return event
    },
  })
}

app.use(router)
app.mount('#app')

环境变量

# .env.production
VITE_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/yyy
VITE_APP_VERSION=0.1.0

DSN 是 Sentry 项目的唯一标识,前端可见(公开)。Sentry 靠域名白名单和速率限制防滥用。


4. 注册账号 → 建项目

  1. sentry.io 注册(免费档 5000 错误/月够个人项目)
  2. Projects → New Project → 选 Vue
  3. 拿到 DSN,填到 .env.production
  4. 本地 pnpm build && pnpm preview,故意触发一个错误看 Sentry 面板能否收到

自建 Sentry:公司项目不希望数据出公网,可以用 Docker 自建(sentry/onpremise)。单机版约 8 核 16G。


5. Release + sourcemap:还原堆栈

生产代码是压缩过的:

Error at a.b (index-a1b2c3.js:1:3847)

没 sourcemap 的话这一坨根本看不懂。两步解决:

5.1 构建时生成并上传 sourcemap

pnpm add -D @sentry/vite-plugin
// vite.config.ts
import { sentryVitePlugin } from '@sentry/vite-plugin'

export default defineConfig({
  plugins: [
    vue(),
    sentryVitePlugin({
      org: 'your-org',
      project: 'in4vue',
      authToken: process.env.SENTRY_AUTH_TOKEN, // 从 Sentry 的 Settings > Developer Settings > Auth Tokens 拿
      release: { name: process.env.VITE_APP_VERSION },
    }),
  ],
  build: {
    sourcemap: true, // 产生 sourcemap
  },
})

流程

  1. pnpm build 生成 dist/assets/*.js.map
  2. sentryVitePlugin 上传到 Sentry(带 release 标识)
  3. 删除本地 .mapsentryVitePlugin 默认上传后会删)——防止用户能下载到源码

Java 对照:类似后端的 log.error + ELK 的堆栈还原,但更"一次性"——source map 是打包那刻的快照。

5.2 release name 怎么取

// package.json 里加一个 script
{
  "scripts": {
    "build": "VITE_APP_VERSION=$(git rev-parse --short HEAD) vite build"
  }
}

6. 手动上报:给错误加上下文

默认 Sentry 捕获所有未处理的 Error 和 Promise.reject业务关键点可以手动上报加信息:

import * as Sentry from '@sentry/vue'

// 1. 捕获异常 + 附加上下文
try {
  await payOrder(orderId)
} catch (err) {
  Sentry.captureException(err, {
    tags: { feature: 'payment' },
    extra: { orderId, userId: user.id, amount: order.amount },
  })
  throw err
}

// 2. 记录消息(非错误)
Sentry.captureMessage('用户多次重试支付', 'warning')

// 3. 设置用户上下文(之后所有错误都会带上)
Sentry.setUser({ id: user.id, username: user.username })

// 4. 退出登录时清
Sentry.setUser(null)

tags vs extra


7. 面包屑(Breadcrumbs)

面包屑 = "错误发生前用户做了什么"。Sentry 自动记录:

查看某个 error 时,底下有一串面包屑告诉你"用户点了登录 → 请求 /api/login → 报 401 → 跳 /login → 又点注册 → 报这个错"。

手动加面包屑

Sentry.addBreadcrumb({
  category: 'ui.action',
  message: '用户点击了"发送问答"按钮',
  level: 'info',
  data: { question: q.slice(0, 50) }, // 前 50 字,别全塞
})

8. 性能监控:BrowserTracing

browserTracingIntegration({ router }) 自动记录:

打开 Sentry 的 Performance 标签:

Java 对照:类似 SkyWalking 的调用链——一次请求的所有耗时分布一目了然。

8.1 采样率的考量

tracesSampleRate: 0.1 // 采集 10% 的事务

高流量项目用 tracesSampler 函数按路径差别采样:

tracesSampler: (context) => {
  // 关键路径全采,其他低采
  if (context.transactionContext.name === '/checkout') return 1.0
  return 0.1
}

9. Session Replay:用户视角录像(可选)

Sentry 的Session Replay能录下用户的浏览器画面(类似 LogRocket):

Sentry.init({
  // ...
  replaysSessionSampleRate: 0.1, // 10% 的用户会话会被录
  replaysOnErrorSampleRate: 1.0, // 发生错误时 100% 录下那一段
  integrations: [
    // ...
    Sentry.replayIntegration({
      maskAllText: true,     // 文字打码(隐私)
      blockAllMedia: true,
    }),
  ],
})

收益:出 bug 时能看到用户的屏幕录像 —— 鼠标轨迹、输入内容(打码后)、网络状态。

代价:流量成本(一段录像几十 KB - 几百 KB),免费档量小。

in4vue 先别开:个人项目这个开销划不来。出 bug 时靠错误堆栈和面包屑足够。


10. 过滤噪音

生产监控会收到一堆和你代码无关的错误:

Sentry.init({
  ignoreErrors: [
    // 浏览器插件
    /extension:\/\//i,
    /chrome-extension:\/\//i,

    // 常见噪音
    'ResizeObserver loop completed with undelivered notifications',
    'ResizeObserver loop limit exceeded',
    'Non-Error promise rejection captured',
    'Script error.',

    // 网络错误(非你的问题)
    'NetworkError when attempting to fetch resource',
    /^Loading chunk \d+ failed\./,  // 用户网络抖了,下一个 chunk 失败
  ],

  denyUrls: [
    // 忽略这些来源的错误(第三方脚本)
    /googleads/i,
    /doubleclick/i,
  ],
})

策略:上线第一周不过滤,看哪些是噪音再加名单——盲目屏蔽可能把真问题也屏蔽了。


11. 隐私与合规

前端监控涉及用户数据,要守规矩:

11.1 PII(个人身份信息)

永远不要上报

做法

11.2 GDPR / 用户同意

欧盟用户有权利要求"不收集我"。正规做法:弹个 Cookie 横幅,用户同意才初始化 Sentry。

if (userConsent) {
  Sentry.init({...})
}

in4vue 是个人学习项目,面向中文开发者,暂时不需要。做给欧美用户的产品必做。

11.3 beforeSend 兜底

在发送前过滤敏感字段:

beforeSend(event) {
  // 清掉请求参数里的敏感字段
  if (event.request?.data) {
    const body = event.request.data as any
    if (typeof body === 'object' && body !== null) {
      const cleaned = { ...body }
      delete cleaned.password
      delete cleaned.token
      delete cleaned.idCard
      event.request.data = cleaned
    }
  }
  return event
}

12. 和其他工具配合

12.1 和 Axios 拦截器

// src/api/request.ts
import * as Sentry from '@sentry/vue'

service.interceptors.response.use(undefined, (error) => {
  const status = error.response?.status
  // 4xx 用户错误通常不上报(太多会淹没真错误)
  if (status && status < 500 && status !== 404) {
    return Promise.reject(error)
  }
  Sentry.captureException(error, {
    tags: { source: 'api' },
    extra: {
      url: error.config?.url,
      method: error.config?.method,
      status,
    },
  })
  return Promise.reject(error)
})

12.2 和 Vue 错误钩子

app.config.errorHandler = (err, instance, info) => {
  console.error('[Vue Error]', err, info)
  Sentry.captureException(err, {
    tags: { vueComponent: info },
  })
}

Sentry 的 Vue integration 已经自动配了 errorHandler,手动写等于重复上报。确认只写一次。

12.3 和 Pinia

Pinia action 抛错 → 被调用方的 await reject → 触发错误钩子。不用额外配置


13. 其他监控指标(Web Vitals)

Google 关注的核心体验指标:

import { onCLS, onFID, onLCP } from 'web-vitals'

onCLS(metric => Sentry.captureMessage(`CLS: ${metric.value}`, 'info'))
onFID(metric => Sentry.captureMessage(`FID: ${metric.value}`, 'info'))
onLCP(metric => Sentry.captureMessage(`LCP: ${metric.value}`, 'info'))

Sentry 的 Performance 已经自动采这些了,不用重复。其他监控平台(如自研)需要手动集成 web-vitals


14. 告警:让你先知道而不是用户

Sentry → Alerts → 规则:

一个朴素的告警矩阵

条件 响应
新错误 Slack 普通消息
1 小时 > 100 次某错误 邮件 + Slack
1 小时影响 > 50 用户 电话/短信(严重)
页面 LCP > 4 秒且持续 30 分钟 Slack 警告

Java 对照:类似 PagerDuty / Prometheus AlertManager 的告警规则。


15. 常见坑点

现象 原因 解法
堆栈显示压缩代码 sourcemap 没上传或 release 对不上 确认 release name 一致
Sentry 没收到任何错误 DSN 错了或 beforeSend 返回了 null 先在 main.ts 里 throw new Error('test')
错误重复上报 既用了 errorHandler 又手动 capture 选一个
发了很多 ResizeObserver 噪音 Chrome 的已知量 ignoreErrors 过滤
免费额度一天用完 采样率太高或没过滤 降采样 + 加 ignoreErrors
用户隐私数据进了监控 没清 PII beforeSend 里过滤
生产 sourcemap 外泄 上传后没删本地 确认构建后 dist/*.map 不存在

16. 心智模型

三层防线:
  1. 代码里的 try-catch        → 局部 UI 恢复
  2. app.config.errorHandler   → Vue 组件兜底
  3. Sentry                    → 全局黑匣子 + 数据沉淀

健康的监控工作流:
  写代码 → 上线 → 每天花 5 分钟看 Sentry issues → 修/忽略/归档
         ↑
         + 新版本 release 后盯一小时,确认错误数没爆炸

坚持每天看比"接入完就不管"重要 10 倍。


17. in4vue 接入步骤总结

  1. pnpm add @sentry/vue
  2. ✅ Sentry 建项目拿 DSN
  3. .env.productionVITE_SENTRY_DSN
  4. main.tsSentry.init
  5. pnpm add -D @sentry/vite-plugin + 配置上传 sourcemap
  6. ✅ 配置 ignoreErrors 过滤常见噪音
  7. beforeSend 过滤 PII
  8. 🟡 配告警规则(新错误 → 邮件)
  9. 🟡 上线后观察一周再精修过滤规则

小练习

  1. 在 Sentry 注册账号建 in4vue 项目,拿到 DSN
  2. 接入 @sentry/vue,生产构建故意 throw new Error('test'),看 Sentry 能否收到
  3. 配置 @sentry/vite-plugin,上传 sourcemap,对比还原前后的堆栈可读性
  4. 给 Axios 拦截器加 5xx 错误上报
  5. 配一条"新错误"告警规则到自己的邮箱

延伸阅读