在现代前端开发中,监控体系是保障用户体验和系统稳定性的关键基础设施。一个完善的前端监控体系能够帮助我们及时发现性能问题、定位错误根源、理解用户行为,从而持续优化产品体验。本文将深入探讨前端监控的三大核心维度:性能监控、错误监控和行为监控。

首屏加载时间 ( FCP - First Contentful Paint )
使用 Performance API 获取 FCP:
const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { console.log(`FCP: ${entry.startTime}ms`); reportMetric('fcp', entry.startTime); } }});observer.observe({ entryTypes: ['paint'] });最大内容绘制 ( LCP - Largest Contentful Paint )
const lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; console.log(`LCP: ${lastEntry.startTime}ms`); reportMetric('lcp', lastEntry.startTime);});lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });累积布局偏移 ( CLS - Cumulative Layout Shift )
let clsValue = 0;const clsObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value; console.log(`CLS: ${clsValue}`); reportMetric('cls', clsValue); } }});clsObserver.observe({ entryTypes: ['layout-shift'] });class PerformanceMonitor { constructor() { this.metrics = {}; } recordPageLoad() { const timing = performance.timing; const metrics = { dnsLookup: timing.domainLookupEnd - timing.domainLookupStart, tcpConnect: timing.connectEnd - timing.connectStart, domParse: timing.domComplete - timing.domLoading, fullLoad: timing.loadEventEnd - timing.navigationStart }; Object.entries(metrics).forEach(([key, value]) => { this.reportMetric(`page_${key}`, value); }); } recordResourceLoad() { performance.getEntriesByType('resource').forEach(resource => { if (resource.duration > 1000) { this.reportMetric('slow_resource', { name: resource.name, duration: resource.duration, type: resource.initiatorType }); } }); } reportMetric(name, value) { fetch('/api/metrics', { method: 'POST', body: JSON.stringify({ name, value, timestamp: Date.now() }), headers: { 'Content-Type': 'application/json' } }); }}const perfMonitor = new PerformanceMonitor();perfMonitor.recordPageLoad();perfMonitor.recordResourceLoad();JavaScript 运行时错误
window.addEventListener('error', (event) => { reportError({ type: 'runtime', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack, timestamp: Date.now() });}, true);window.addEventListener('unhandledrejection', (event) => { reportError({ type: 'promise', message: event.reason?.message || 'Unhandled Promise Rejection', stack: event.reason?.stack, timestamp: Date.now() });});Vue 错误捕获
import { createApp } from 'vue';const app = createApp(App);app.config.errorHandler = (err, instance, info) => { reportError({ type: 'vue', message: err.message, stack: err.stack, component: instance?.name || 'Anonymous', lifecycleHook: info, timestamp: Date.now() });};React 错误边界
import React from 'react';class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { reportError({ type: 'react', message: error.message, stack: error.stack, componentStack: errorInfo.componentStack, timestamp: Date.now() }); } render() { if (this.state.hasError) { return <div>页面出错了,请稍后刷新</div>; } return this.props.children; }}window.addEventListener('error', (event) => { if (event.target instanceof HTMLImageElement) { reportError({ type: 'resource', resourceType: 'image', url: event.target.src, timestamp: Date.now() }); }}, true);const originalFetch = window.fetch;window.fetch = async function(...args) { try { const response = await originalFetch(...args); if (!response.ok) { reportError({ type: 'http', url: args[0], status: response.status, method: args[1]?.method || 'GET' }); } return response; } catch (error) { reportError({ type: 'http', url: args[0], message: error.message, method: args[1]?.method || 'GET' }); throw error; }};class BehaviorTracker { constructor() { this.sessionId = this.generateSessionId(); this.pageViewTime = 0; } trackClick(element) { const eventData = { type: 'click', element: element.tagName, className: element.className, text: element.textContent?.slice(0, 50), x: element.getBoundingClientRect().left, y: element.getBoundingClientRect().top, pageUrl: window.location.href, timestamp: Date.now() }; this.reportBehavior(eventData); } trackPageView() { const startTime = Date.now(); window.addEventListener('beforeunload', () => { const duration = Date.now() - startTime; this.reportBehavior({ type: 'pageview', url: window.location.href, duration, sessionId: this.sessionId, timestamp: Date.now() }); }); } trackScroll() { let scrollTimeout; window.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { const scrollDepth = Math.round( (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100 ); this.reportBehavior({ type: 'scroll', scrollDepth, timestamp: Date.now() }); }, 500); }); } reportBehavior(data) { navigator.sendBeacon('/api/behavior', JSON.stringify(data)); } generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }}const tracker = new BehaviorTracker();tracker.trackPageView();tracker.trackScroll();document.addEventListener('click', (event) => { tracker.trackClick(event.target);});class Analytics { constructor() { this.userActions = []; } recordAction(action) { this.userActions.push({ ...action, sessionId: this.getSessionId(), userId: this.getUserId() }); } getSessionId() { return localStorage.getItem('session_id') || (localStorage.setItem('session_id', `sess_${Date.now()}`), localStorage.getItem('session_id')); } getUserId() { return localStorage.getItem('user_id') || 'anonymous'; } analyzePerformanceAfterAction(actionType) { const actions = this.userActions.filter(a => a.type === actionType); const recentActions = actions.slice(-10); return { avgResponseTime: recentActions.reduce((sum, a) => sum + (a.responseTime || 0), 0) / recentActions.length, errorRate: recentActions.filter(a => a.error).length / recentActions.length }; }}class MonitorService { constructor(config) { this.endpoint = config.endpoint; this.appId = config.appId; this.queue = []; this.flushInterval = 5000; this.init(); } init() { setInterval(() => this.flush(), this.flushInterval); window.addEventListener('beforeunload', () => this.flush()); } report(type, data) { const payload = { appId: this.appId, type, data, timestamp: Date.now(), userAgent: navigator.userAgent, url: window.location.href, referrer: document.referrer }; this.queue.push(payload); if (type === 'error') { this.flush(); } } flush() { if (this.queue.length === 0) return; const data = [...this.queue]; this.queue = []; navigator.sendBeacon(`${this.endpoint}/batch`, JSON.stringify(data)); }}const monitor = new MonitorService({ endpoint: 'https://monitor.example.com', appId: 'frontend-app-001'});const alertRules = { errorRate: { threshold: 0.05, window: 300 }, fcp: { threshold: 2500 }, lcp: { threshold: 4000 }, cls: { threshold: 0.25 }, apiErrorRate: { threshold: 0.1, window: 60 }};function checkAlerts(metric, value) { const rule = alertRules[metric]; if (!rule) return; if (value > rule.threshold) { sendAlert({ metric, value, threshold: rule.threshold, timestamp: Date.now() }); }}一个完善的前端监控体系应该包含:
通过统一的数据上报和告警机制,我们可以:
记住:监控不是为了发现问题,而是为了预防问题。建立完善的监控体系,让前端开发更加从容!
以上就是一文详解前端性能监控的三大核心维度的详细内容,更多关于前端性能监控的资料请关注本站其它相关文章!
《Tales of Demagost: Exodus》完美复刻了《Mass Effect》的战斗体验,但角色扮演部分让我担忧
内幕人士透露《The Simpsons: Hit & Run》将推出重制版
Windsurf国内访问限制:地区、网络与账号兼容性说明
《流放之路2》0.5赛季武圣剧情开荒详尽攻略-剧情任务装备选择核心解析
极限竞速地平线6鲤鱼旗拍摄位置-鲤鱼旗具体分布与传送点详解
LEVIATÁN以2-1战胜Global Esports:取得伦敦赛程开门红