前端监控与错误追踪实战指南

前端监控系统概览 完整的前端监控系统应该包含: 性能监控 - 页面加载、资源、交互性能 错误追踪 - JavaScript错误、资源错误、网络错误 用户体验 - Core Web Vitals、用户行为 业务指标 - 转化率、留存率等 性能监控 Performance API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // 页面加载性能 function measurePagePerformance() { if (!window.performance) return const timing = window.performance.timing const navigation = performance.getEntriesByType('navigation')[0] const metrics = { // DNS查询时间 dns: timing.domainLookupEnd - timing.domainLookupStart, // TCP连接时间 tcp: timing.connectEnd - timing.connectStart, // 请求响应时间 request: timing.responseEnd - timing.requestStart, // DOM解析时间 domParse: timing.domComplete - timing.domLoading, // 首次内容绘制 fcp: navigation?.loadEventEnd, // 首次输入延迟 fid: 0, // 最大内容绘制 lcp: 0, // 首次字节时间 ttfb: timing.responseStart - timing.navigationStart, // 页面完全加载时间 loadTime: timing.loadEventEnd - timing.navigationStart } return metrics } Core Web Vitals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // LCP (Largest Contentful Paint) function measureLCP() { new PerformanceObserver((list) => { const entries = list.getEntries() const lastEntry = entries[entries.length - 1] console.log('LCP:', lastEntry.startTime) }).observe({ entryTypes: ['largest-contentful-paint'] }) } // FID (First Input Delay) function measureFID() { new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log('FID:', entry.processingStart - entry.startTime) } }).observe({ entryTypes: ['first-input'] }) } // CLS (Cumulative Layout Shift) function measureCLS() { let clsValue = 0 new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value } } console.log('CLS:', clsValue) }).observe({ entryTypes: ['layout-shift'] }) } 资源加载监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function monitorResources() { const resources = performance.getEntriesByType('resource') resources.forEach(resource => { const data = { name: resource.name, type: resource.initiatorType, duration: resource.duration, size: resource.transferSize, // 资源加载时间分解 dns: resource.domainLookupEnd - resource.domainLookupStart, tcp: resource.connectEnd - resource.connectStart, ssl: resource.secureConnectionStart > 0 ? resource.connectEnd - resource.secureConnectionStart : 0, ttfb: resource.responseStart - resource.requestStart, download: resource.responseEnd - resource.responseStart } // 检测慢资源 if (data.duration > 1000) { console.warn('Slow resource:', data) } }) } 错误追踪 全局错误捕获 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // JavaScript错误 window.addEventListener('error', (event) => { const error = { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, error: event.error?.stack, type: 'javascript', timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent } sendErrorToServer(error) }) // Promise错误 window.addEventListener('unhandledrejection', (event) => { const error = { reason: event.reason, promise: event.promise, type: 'promise', timestamp: Date.now(), url: window.location.href } sendErrorToServer(error) }) // 资源加载错误 window.addEventListener('error', (event) => { if (event.target !== window) { const error = { tagName: event.target.tagName, src: event.target.src || event.target.href, type: 'resource', timestamp: Date.now() } sendErrorToServer(error) } }, true) Vue错误处理 1 2 3 4 5 6 7 8 9 10 11 12 13 // Vue应用全局错误处理 app.config.errorHandler = (err, instance, info) => { const error = { message: err.message, stack: err.stack, component: instance?.$options.name, info: info, type: 'vue', timestamp: Date.now() } sendErrorToServer(error) } 错误聚合上报 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 class ErrorTracker { constructor(config) { this.config = { batchSize: 10, flushInterval: 5000, ...config } this.errorQueue = [] this.startFlushTimer() } addError(error) { this.errorQueue.push({ ...error, timestamp: Date.now(), url: window.location.href, userAgent: navigator.userAgent, userId: this.getUserId() }) if (this.errorQueue.length >= this.config.batchSize) { this.flush() } } async flush() { if (this.errorQueue.length === 0) return const errors = [...this.errorQueue] this.errorQueue = [] try { await fetch('/api/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ errors }) }) } catch (e) { console.error('Failed to send errors:', e) // 重新入队 this.errorQueue.unshift(...errors) } } startFlushTimer() { setInterval(() => { this.flush() }, this.config.flushInterval) } getUserId() { // 获取或生成用户ID let userId = localStorage.getItem('tracker_user_id') if (!userId) { userId = 'user_' + Date.now() + '_' + Math.random() localStorage.setItem('tracker_user_id', userId) } return userId } } 用户行为追踪 页面访问追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class PageTracker { constructor() { this.currentPage = null this.pageStartTime = null this.init() } init() { // 页面加载完成 window.addEventListener('load', () => { this.trackPageView() }) // SPA路由变化 this.trackRouteChanges() } trackPageView() { this.pageStartTime = Date.now() this.currentPage = { url: window.location.pathname, referrer: document.referrer, title: document.title, timestamp: this.pageStartTime } this.sendEvent('pageview', this.currentPage) } trackRouteChanges() { // 监听路由变化(需要根据具体框架实现) let lastUrl = location.href new MutationObserver(() => { const url = location.href if (url !== lastUrl) { lastUrl = url this.trackPageView() } }).observe(document, { subtree: true, childList: true }) } trackEvent(category, action, label, value) { this.sendEvent('event', { category, action, label, value, timestamp: Date.now() }) } sendEvent(type, data) { // 发送到分析服务器 fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type, data, userId: this.getUserId(), sessionId: this.getSessionId() }) }) } } 交互追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class InteractionTracker { constructor() { this.init() } init() { // 点击追踪 document.addEventListener('click', (e) => { const target = e.target const data = { tagName: target.tagName, id: target.id, className: target.className, text: target.textContent?.slice(0, 50), x: e.clientX, y: e.clientY } this.track('click', data) }, true) // 滚动追踪 let scrollTimeout document.addEventListener('scroll', () => { clearTimeout(scrollTimeout) scrollTimeout = setTimeout(() => { this.track('scroll', { scrollDepth: this.getScrollDepth() }) }, 100) }) } getScrollDepth() { const scrollTop = window.pageYOffset const docHeight = document.documentElement.scrollHeight const winHeight = window.innerHeight return Math.round((scrollTop / (docHeight - winHeight)) * 100) } track(type, data) { // 发送交互数据 } } 完整监控方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 class MonitorSDK { constructor(config) { this.config = config this.errorTracker = new ErrorTracker(config.error) this.pageTracker = new PageTracker() this.interactionTracker = new InteractionTracker() } // 性能指标 startPerformanceMonitoring() { if ('PerformanceObserver' in window) { // 监控LCP this.observeLCP() // 监控FID this.observeFID() // 监控CLS this.observeCLS() } // 页面卸载时发送最终数据 window.addEventListener('beforeunload', () => { this.flush() }) } observeLCP() { new PerformanceObserver((list) => { const entries = list.getEntries() const lastEntry = entries[entries.length - 1] this.reportMetric('LCP', lastEntry.startTime) }).observe({ entryTypes: ['largest-contentful-paint'] }) } observeFID() { new PerformanceObserver((list) => { for (const entry of list.getEntries()) { this.reportMetric('FID', entry.processingStart - entry.startTime) } }).observe({ entryTypes: ['first-input'] }) } observeCLS() { let clsValue = 0 new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value } } this.reportMetric('CLS', clsValue) }).observe({ entryTypes: ['layout-shift'] }) } reportMetric(name, value) { // 上报性能指标 fetch('/api/metrics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ metric: name, value, timestamp: Date.now(), url: window.location.href }) }) } flush() { this.errorTracker.flush() } } // 初始化 const monitor = new MonitorSDK({ error: { batchSize: 10, flushInterval: 5000 } }) monitor.startPerformanceMonitoring() 最佳实践 采样策略 - 高流量时采用采样 数据压缩 - 减少传输数据量 本地缓存 - 离线时缓存数据 隐私保护 - 遵守隐私法规 性能影响 - 最小化监控对性能的影响 总结 前端监控系统是保障应用质量的重要工具。通过全面的性能监控、错误追踪和用户行为分析,可以及时发现并解决问题,提升用户体验。 ...