线上项目一旦出问题,最被动的往往是开发者:用户已经骂了半天,团队却毫无察觉。要改变这种局面,就得在项目里建立一套完整的错误监控与日志上报机制。Vue 3 在错误处理上提供了官方的钩子,配合 Sentry 这类成熟平台,再加上一些自定义的上报逻辑,基本可以覆盖绝大多数线上异常场景。本文从错误捕获的原理讲起,逐步展开 Sentry 的集成细节和自建上报方案的设计思路。

一、Vue 3 的错误捕获机制:先搞清楚错误从哪来
浏览器里未被处理的错误最终会触发 window.onerror 或 unhandledrejection 事件,这是错误监控的地基。但在 Vue 项目中,组件渲染、生命周期钩子、事件处理器里抛出的错误默认会被 Vue 自己拦截,不会直接冒泡到 window.onerror,而是交给应用实例上的全局错误处理器。这是很多初学者接了 window.onerror 却收不到组件报错的原因。
Vue 3 暴露的入口是 app.config.errorHandler,它在应用创建后注册一次即可:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.errorHandler = (err, instance, info) => {
// err 是原始错误对象
// instance 是抛出错误的组件实例
// info 是错误来源描述,例如 "setup function"
console.error('全局捕获:', err, info)
reportError({
message: err.message,
stack: err.stack,
source: info,
component: instance?.$options?.name || 'Anonymous'
})
}
app.mount('#app')这里有几个细节值得注意。第一,errorHandler 只捕获未处理的错误,如果组件内部用 try...catch 消化掉了异常,全局钩子不会触发。第二,info 参数是 Vue 3 新增的,能告诉你错误发生在哪个阶段,比如渲染函数、setup 执行期还是事件回调里,排查时非常有用。第三,异步错误需要单独处理:Promise 未捕获的 rejection 要监听 unhandledrejection 事件;如果你用了 Vue Router,路由守卫里的错误需要监听 router.onError;组件上的错误还可以通过 onErrorCaptured 钩子在父级逐层捕获,返回 false 可以阻止错误继续向上传播。
二、Sentry 集成:从接入到 source map 还原
自建日志系统成本不低,Sentry 提供了开箱即用的方案。接入 Vue 3 项目只需安装 @sentry/vue 并在入口文件初始化。与旧版的 @sentry/browser 不同,@sentry/vue 针对 Vue 做了深度适配,初始化时直接传入应用实例,内部会自动注册错误处理器、集成路由追踪,还能识别组件树信息:
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import * as Sentry from '@sentry/vue'
import App from './App.vue'
const router = createRouter({
history: createWebHistory(),
routes: []
})
const app = createApp(App)
Sentry.init({
app,
dsn: 'https://your-dsn@sentry.example.org/0',
environment: import.meta.env.MODE,
release: 'my-app@1.2.0',
integrations: [
Sentry.browserTracingIntegration({ router }),
Sentry.replayIntegration()
],
tracesSampleRate: 0.2,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0
})
app.use(router)
app.mount('#app')配置里最关键的是 release 版本号与 source map 的配合。生产环境的代码经过压缩混淆后,错误堆栈里全是类似 a.js:1:28451 的坐标,根本没法定位。正确做法是构建时上传 source map 到 Sentry,让平台自动还原出源码位置。以 Vite 为例,可以使用官方推荐的 @sentry/vite-plugin 插件,在构建产物生成后自动上传,并在上传完成后删除本地 map 文件,避免泄漏源码到线上环境。
除了自动捕获,Sentry 还提供了手动埋点能力。比如接口请求失败、用户操作异常这类业务层面的错误,可以用 Sentry.captureException 上报;纯文本的调试线索用 Sentry.captureMessage;同时配合 Sentry.setUser 绑定用户身份,配合 beforeSend 钩子做过滤与脱敏:
Sentry.setUser({ id: user.id, email: user.email })
// 初始化时配置 beforeSend
Sentry.init({
// ...其他配置
beforeSend(event) {
// 过滤掉无意义的错误
if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
return null
}
// 对敏感字段脱敏
if (event.request?.headers) {
delete event.request.headers.Authorization
}
return event
}
})
// 手动上报接口异常
try {
await api.submitOrder(payload)
} catch (err) {
Sentry.captureException(err, {
tags: { module: 'order', api: '/api/order/submit' },
extra: { payload }
})
}三、自建日志上报:分级、限流与可靠投递
Sentry 免费额度有限,或者公司要求数据内网闭环时,就需要自建上报。一个能用的方案至少要解决四个问题:错误分级、上报限流、可靠投递和上下文补充。下面是一份参考实现:
class ErrorReporter {
constructor(endpoint) {
this.endpoint = endpoint
this.queue = []
this.timer = null
this.retryCount = new Map()
}
// 错误分级:error 必发,warning/info 采样发送
report(log) {
const item = {
level: log.level || 'error',
message: log.message,
stack: log.stack,
url: location.href,
ua: navigator.userAgent,
timestamp: Date.now()
}
if (item.level !== 'error' && Math.random() > 0.3) return
this.queue.push(item)
this.scheduleFlush()
}
scheduleFlush() {
if (this.timer) return
this.timer = setTimeout(() => this.flush(), 3000)
}
async flush() {
const batch = this.queue.splice(0, 20)
this.timer = null
if (!batch.length) return
try {
// 用 sendBeacon 保证页面关闭时也能发出
const blob = new Blob([JSON.stringify(batch)], { type: 'application/json' })
const ok = navigator.sendBeacon(this.endpoint, blob)
if (!ok) throw new Error('sendBeacon rejected')
} catch (e) {
// 失败放回队列,带重试上限,防止死循环
batch.forEach(item => {
const count = (this.retryCount.get(item.message) || 0) + 1
if (count <= 3) {
this.retryCount.set(item.message, count)
this.queue.push(item)
}
})
setTimeout(() => this.scheduleFlush(), 10000)
}
}
}这份实现里有几个工程要点。传输方式上,sendBeacon 是首选,它不阻塞页面卸载,就算用户在报错瞬间关掉标签页,请求也能发出;如果不支持则回退到 fetch 加 keepalive 选项。限流方面,除了错误分级采样,还应该在服务端按错误指纹做聚合,指纹可以取错误 message 加堆栈第一行做哈希,把上万条重复错误折叠成一个问题,否则一次线上事故就能把存储打满。
上下文补充决定了排查效率。上报时尽量带上路由地址、用户标识、最近一次的操作序列(可以维护一个环形数组记录最近 20 条用户行为)、设备信息以及前端版本号。有了这些,你看到的不只是一条堆栈,而是某用户在支付页点了两次提交按钮后触发空指针这样的完整故事线,定位问题的时间会缩短一个量级。
四、方案选型与几个常见的坑
Sentry 和自建方案并非二选一。中小团队或项目初期,直接用 Sentry 最划算,零维护成本,会话回放、告警、性能监控都是现成的;等数据量上来或合规要求变严,再考虑把高频的业务日志切到自建通道,Sentry 只保留致命错误。混合使用时注意统一错误指纹规则,避免两边各报各的,告警重复轰炸值班同学。
最后提醒三个容易踩的坑。一是跨域脚本报错时堆栈只有 Script error 字样,需要给静态资源响应头加上跨域许可并给 script 标签声明 crossorigin 属性;二是全局错误处理器本身别再抛异常,否则可能形成递归上报,写的时候务必包一层 try...catch;三是监控代码要做防御性编程,第三方脚本挂了都不应影响主业务渲染。监控体系的价值在于长期稳定运行,宁可少报一条,也不能拖垮页面。