应用监控
在生产环境中,监控 Vue 应用的性能和健康状况至关重要。本指南将介绍如何设置全面的监控系统来跟踪应用的各个方面。
错误监控
全局错误处理
设置全局错误处理器来捕获和报告应用中的错误:
js
import { createApp } from 'vue'
import * as Sentry from '@sentry/vue'
const app = createApp(App)
// 配置 Sentry 错误监控
Sentry.init({
app,
dsn: 'YOUR_SENTRY_DSN',
environment: process.env.NODE_ENV,
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
}),
],
tracesSampleRate: 1.0,
})
// 自定义错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('Vue Error:', err)
console.error('Component:', instance)
console.error('Info:', info)
// 发送错误到监控服务
Sentry.captureException(err, {
contexts: {
vue: {
componentName: instance?.$options.name,
propsData: instance?.$props,
info: info
}
}
})
}
// 捕获未处理的 Promise 拒绝
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled promise rejection:', event.reason)
Sentry.captureException(event.reason)
})
组件级错误边界
创建错误边界组件来优雅地处理组件错误:
vue
<template>
<div>
<div v-if="hasError" class="error-boundary">
<h2>出现了错误</h2>
<p>{{ errorMessage }}</p>
<button @click="retry">重试</button>
</div>
<slot v-else />
</div>
</template>
<script setup>
import { ref, onErrorCaptured } from 'vue'
import * as Sentry from '@sentry/vue'
const hasError = ref(false)
const errorMessage = ref('')
onErrorCaptured((err, instance, info) => {
hasError.value = true
errorMessage.value = err.message
// 报告错误
Sentry.captureException(err, {
tags: {
errorBoundary: true
},
contexts: {
vue: {
componentName: instance?.$options.name,
info: info
}
}
})
return false // 阻止错误继续传播
})
const retry = () => {
hasError.value = false
errorMessage.value = ''
}
</script>
性能监控
Core Web Vitals 监控
监控关键的性能指标:
js
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
// 监控 Core Web Vitals
function sendToAnalytics(metric) {
// 发送到分析服务
gtag('event', metric.name, {
event_category: 'Web Vitals',
event_label: metric.id,
value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
non_interaction: true,
})
}
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getTTFB(sendToAnalytics)
Vue 组件性能监控
js
// 性能监控 mixin
const performanceMonitorMixin = {
beforeCreate() {
this._startTime = performance.now()
},
mounted() {
const mountTime = performance.now() - this._startTime
// 记录组件挂载时间
if (mountTime > 100) { // 只记录超过 100ms 的组件
console.warn(`Slow component mount: ${this.$options.name} took ${mountTime}ms`)
// 发送到监控服务
gtag('event', 'slow_component_mount', {
event_category: 'Performance',
event_label: this.$options.name,
value: Math.round(mountTime)
})
}
},
beforeUpdate() {
this._updateStartTime = performance.now()
},
updated() {
if (this._updateStartTime) {
const updateTime = performance.now() - this._updateStartTime
if (updateTime > 50) { // 只记录超过 50ms 的更新
console.warn(`Slow component update: ${this.$options.name} took ${updateTime}ms`)
gtag('event', 'slow_component_update', {
event_category: 'Performance',
event_label: this.$options.name,
value: Math.round(updateTime)
})
}
}
}
}
// 全局应用 mixin
app.mixin(performanceMonitorMixin)
路由性能监控
js
import { createRouter } from 'vue-router'
const router = createRouter({
// ... 路由配置
})
// 监控路由导航性能
router.beforeEach((to, from, next) => {
// 记录导航开始时间
to.meta.startTime = performance.now()
next()
})
router.afterEach((to, from) => {
if (to.meta.startTime) {
const navigationTime = performance.now() - to.meta.startTime
// 记录路由导航时间
gtag('event', 'page_view', {
page_title: to.name,
page_location: to.fullPath,
custom_map: {
navigation_time: Math.round(navigationTime)
}
})
// 慢导航警告
if (navigationTime > 1000) {
console.warn(`Slow navigation to ${to.path}: ${navigationTime}ms`)
}
}
})
用户行为监控
用户交互追踪
js
// 用户交互追踪指令
const trackingDirective = {
mounted(el, binding) {
const { event, category, label } = binding.value
el.addEventListener('click', () => {
gtag('event', event || 'click', {
event_category: category || 'User Interaction',
event_label: label || el.textContent
})
})
}
}
app.directive('track', trackingDirective)
使用示例:
vue
<template>
<button v-track="{ event: 'button_click', category: 'CTA', label: 'Sign Up' }">
注册
</button>
</template>
页面停留时间监控
js
class PageTimeTracker {
constructor() {
this.startTime = null
this.isVisible = true
this.totalTime = 0
this.setupVisibilityTracking()
}
startTracking(pageName) {
this.pageName = pageName
this.startTime = performance.now()
this.totalTime = 0
}
stopTracking() {
if (this.startTime && this.isVisible) {
this.totalTime += performance.now() - this.startTime
}
if (this.totalTime > 0) {
gtag('event', 'page_time', {
event_category: 'Engagement',
event_label: this.pageName,
value: Math.round(this.totalTime / 1000) // 转换为秒
})
}
}
setupVisibilityTracking() {
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
if (this.isVisible && this.startTime) {
this.totalTime += performance.now() - this.startTime
}
this.isVisible = false
} else {
this.isVisible = true
this.startTime = performance.now()
}
})
}
}
const pageTimeTracker = new PageTimeTracker()
// 在路由中使用
router.beforeEach((to, from, next) => {
if (from.name) {
pageTimeTracker.stopTracking()
}
next()
})
router.afterEach((to) => {
pageTimeTracker.startTracking(to.name)
})
资源监控
网络请求监控
js
// Axios 拦截器用于监控 API 请求
import axios from 'axios'
axios.interceptors.request.use(
config => {
config.metadata = { startTime: performance.now() }
return config
},
error => {
return Promise.reject(error)
}
)
axios.interceptors.response.use(
response => {
const duration = performance.now() - response.config.metadata.startTime
// 记录 API 响应时间
gtag('event', 'api_response_time', {
event_category: 'API',
event_label: response.config.url,
value: Math.round(duration)
})
// 慢请求警告
if (duration > 2000) {
console.warn(`Slow API request: ${response.config.url} took ${duration}ms`)
}
return response
},
error => {
const duration = error.config?.metadata ?
performance.now() - error.config.metadata.startTime : 0
// 记录 API 错误
gtag('event', 'api_error', {
event_category: 'API',
event_label: error.config?.url || 'unknown',
value: error.response?.status || 0
})
// 发送错误到 Sentry
Sentry.captureException(error, {
tags: {
type: 'api_error'
},
contexts: {
request: {
url: error.config?.url,
method: error.config?.method,
duration: duration
}
}
})
return Promise.reject(error)
}
)
资源加载监控
js
// 监控资源加载性能
function monitorResourceLoading() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource') {
const duration = entry.responseEnd - entry.startTime
// 记录慢资源
if (duration > 1000) {
console.warn(`Slow resource: ${entry.name} took ${duration}ms`)
gtag('event', 'slow_resource', {
event_category: 'Performance',
event_label: entry.name,
value: Math.round(duration)
})
}
// 记录失败的资源
if (entry.transferSize === 0 && entry.decodedBodySize === 0) {
gtag('event', 'resource_error', {
event_category: 'Error',
event_label: entry.name
})
}
}
}
})
observer.observe({ entryTypes: ['resource'] })
}
monitorResourceLoading()
实时监控仪表板
自定义监控组件
vue
<template>
<div class="monitoring-dashboard" v-if="isDev">
<div class="metric">
<label>组件数量:</label>
<span>{{ componentCount }}</span>
</div>
<div class="metric">
<label>内存使用:</label>
<span>{{ memoryUsage }}MB</span>
</div>
<div class="metric">
<label>FPS:</label>
<span>{{ fps }}</span>
</div>
<div class="metric">
<label>API 请求:</label>
<span>{{ apiRequestCount }}</span>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const isDev = process.env.NODE_ENV === 'development'
const componentCount = ref(0)
const memoryUsage = ref(0)
const fps = ref(0)
const apiRequestCount = ref(0)
let frameCount = 0
let lastTime = performance.now()
let animationId = null
function updateMetrics() {
// 更新 FPS
frameCount++
const currentTime = performance.now()
if (currentTime - lastTime >= 1000) {
fps.value = Math.round((frameCount * 1000) / (currentTime - lastTime))
frameCount = 0
lastTime = currentTime
}
// 更新内存使用 (如果支持)
if (performance.memory) {
memoryUsage.value = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024)
}
animationId = requestAnimationFrame(updateMetrics)
}
onMounted(() => {
if (isDev) {
updateMetrics()
// 监听组件创建/销毁
const originalCreateApp = createApp
// ... 实现组件计数逻辑
}
})
onUnmounted(() => {
if (animationId) {
cancelAnimationFrame(animationId)
}
})
</script>
<style scoped>
.monitoring-dashboard {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 9999;
}
.metric {
margin: 5px 0;
}
.metric label {
margin-right: 10px;
}
</style>
监控最佳实践
1. 采样策略
js
// 根据环境和用户设置不同的采样率
const getSampleRate = () => {
if (process.env.NODE_ENV === 'development') {
return 1.0 // 开发环境 100% 采样
}
if (Math.random() < 0.1) {
return 1.0 // 10% 的用户进行详细监控
}
return 0.1 // 其他用户 10% 采样
}
Sentry.init({
tracesSampleRate: getSampleRate(),
// ...
})
2. 性能预算
js
// 设置性能预算
const PERFORMANCE_BUDGET = {
componentMount: 100, // 组件挂载不超过 100ms
apiRequest: 2000, // API 请求不超过 2s
routeNavigation: 1000, // 路由导航不超过 1s
bundleSize: 500 * 1024, // 包大小不超过 500KB
}
// 检查性能预算
function checkPerformanceBudget(metric, value) {
if (value > PERFORMANCE_BUDGET[metric]) {
console.warn(`Performance budget exceeded: ${metric} took ${value}ms (budget: ${PERFORMANCE_BUDGET[metric]}ms)`)
// 发送警告到监控服务
gtag('event', 'performance_budget_exceeded', {
event_category: 'Performance',
event_label: metric,
value: value
})
}
}
3. 监控数据隐私
js
// 敏感数据过滤
Sentry.init({
beforeSend(event) {
// 过滤敏感信息
if (event.request?.data) {
event.request.data = filterSensitiveData(event.request.data)
}
if (event.extra) {
event.extra = filterSensitiveData(event.extra)
}
return event
}
})
function filterSensitiveData(data) {
const sensitiveKeys = ['password', 'token', 'email', 'phone']
const filtered = { ...data }
sensitiveKeys.forEach(key => {
if (filtered[key]) {
filtered[key] = '[FILTERED]'
}
})
return filtered
}
通过实施这些监控策略,你可以获得对 Vue 应用性能和健康状况的全面了解,从而能够主动识别和解决问题,提供更好的用户体验。