前端监控与错误上报(Sentry)
1. 后端视角理解前端监控
后端写日志你很熟:log.error(...) + ELK / Loki / Splunk / 阿里云 SLS。出了事去查日志就行。
前端没有"服务器"这么一说——代码跑在千万用户各自的浏览器里:
- 你看不到
console.error - 不知道用户点了什么才报错
- 不知道线上是不是快
- 不知道不同地区、不同浏览器的体验是不是一致
没有监控 = 盲人摸象。
Java 对照:类似 SkyWalking / Sentry / Arthas 的前端版——给每个用户的浏览器装一个"黑匣子",出事了能回放。
常见方案:
- Sentry(错误 + 性能,闭源但免费额度可观,生态最全)
- LogRocket(带会话回放的高级方案)
- 阿里云 ARMS 前端监控(国内业务合规友好)
- 自建(用 Elastic APM / OpenTelemetry + 自己写 SDK)
in4vue 用 Sentry。
2. Sentry 能回答什么
Sentry 接入后能看到:
-
🟥 Issues:所有报错,按错误相似度聚合
- 错误堆栈 + 原始源码(用 sourcemap 还原)
- 用户操作面包屑(点了什么、打了什么字)
- 设备/浏览器/地区分布
- 首次出现 / 最近出现 / 影响用户数
-
📈 Performance:页面加载、路由切换、接口请求的耗时
- P50 / P95 / P99
- 慢的瀑布图
- 哪个接口在拖慢页面
-
🧭 Releases:按版本号看趋势
- 新版本有没有引入新错误?
- 新版本是变快还是变慢?
-
💬 Feedback:用户报告按钮("出错了?描述下")
核心 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. 注册账号 → 建项目
- 到 sentry.io 注册(免费档 5000 错误/月够个人项目)
- Projects → New Project → 选 Vue
- 拿到 DSN,填到
.env.production - 本地
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
},
})
流程:
pnpm build生成dist/assets/*.js.mapsentryVitePlugin上传到 Sentry(带 release 标识)- 删除本地
.map(sentryVitePlugin默认上传后会删)——防止用户能下载到源码
Java 对照:类似后端的 log.error + ELK 的堆栈还原,但更"一次性"——source map 是打包那刻的快照。
5.2 release name 怎么取
- 小项目:用
package.json的version("version": "0.1.0") - 大项目:用
git rev-parse HEAD的 short hash(每次 commit 一个 release)
// 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:
tags—— 可搜索可过滤(如"只看 payment 相关错误")extra—— 只显示,不能搜索
7. 面包屑(Breadcrumbs)
面包屑 = "错误发生前用户做了什么"。Sentry 自动记录:
- 控制台输出(
console.log也会收) - HTTP 请求
- 路由切换
- 点击事件(可选)
查看某个 error 时,底下有一串面包屑告诉你"用户点了登录 → 请求 /api/login → 报 401 → 跳 /login → 又点注册 → 报这个错"。
手动加面包屑:
Sentry.addBreadcrumb({
category: 'ui.action',
message: '用户点击了"发送问答"按钮',
level: 'info',
data: { question: q.slice(0, 50) }, // 前 50 字,别全塞
})
8. 性能监控:BrowserTracing
browserTracingIntegration({ router }) 自动记录:
- 页面加载:DNS / TCP / SSL / TTFB / 渲染耗时
- 路由切换:每次
router.push到"页面可交互"的耗时 - 接口请求:每个 axios/fetch 的时长
打开 Sentry 的 Performance 标签:
- 按事务(transaction)分组看 P50/P95
- 点进去看瀑布图——"这次切到 NoteList 花了 1.2 秒,其中 /api/notes 等了 800ms"
Java 对照:类似 SkyWalking 的调用链——一次请求的所有耗时分布一目了然。
8.1 采样率的考量
tracesSampleRate: 0.1 // 采集 10% 的事务
- 100% 采样:数据全但贵(免费额度快速耗光)
- 10% 采样:统计意义足够,成本可控
- 生产推荐 0.1 - 0.3
高流量项目用 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. 过滤噪音
生产监控会收到一堆和你代码无关的错误:
- 浏览器插件注入的脚本报错
- 广告拦截器引发的 CORS 错
- 某些浏览器的弃用 API
- Safari 的
Non-Error promise rejection
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(个人身份信息)
永远不要上报:
- 身份证号、银行卡号
- 完整的手机号、邮箱(部分脱敏可以)
- 登录密码(哪怕是 hash 的)
- 地址、真实姓名
做法:
setUser只传 user id(匿名化),别传用户名和邮箱extra/tags里的业务字段过滤敏感项- Session Replay 用
maskAllText: true
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/钉钉
- 某错误一小时超过 100 次 → 邮件通知
- P95 响应时间大于 2 秒 → 紧急告警
一个朴素的告警矩阵:
| 条件 | 响应 |
|---|---|
| 新错误 | 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 接入步骤总结
- ✅
pnpm add @sentry/vue - ✅ Sentry 建项目拿 DSN
- ✅
.env.production填VITE_SENTRY_DSN - ✅
main.ts里Sentry.init - ✅
pnpm add -D @sentry/vite-plugin+ 配置上传 sourcemap - ✅ 配置
ignoreErrors过滤常见噪音 - ✅
beforeSend过滤 PII - 🟡 配告警规则(新错误 → 邮件)
- 🟡 上线后观察一周再精修过滤规则
小练习
- 在 Sentry 注册账号建
in4vue项目,拿到 DSN - 接入
@sentry/vue,生产构建故意throw new Error('test'),看 Sentry 能否收到 - 配置
@sentry/vite-plugin,上传 sourcemap,对比还原前后的堆栈可读性 - 给 Axios 拦截器加 5xx 错误上报
- 配一条"新错误"告警规则到自己的邮箱