在现代前端工程中,监控是保障用户体验和服务稳定性的基础设施。本文围绕性能监控、错误监控和行为监控三大维度,给出可直接落地的代码实现,涵盖 Web API 的 PerformanceObserver、全局错误钩子、自定义埋点、数据上报以及告警规则设计。
- /* 以下代码均基于现代浏览器,适用于 Vue/React 项目集成 */
- // 1. 性能监控:核心指标采集
- 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'] });
- 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'] });
- 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'] });
复制代码
使用 PerformanceObserver 监听首屏渲染(FCP)、最大内容绘制(LCP)和布局偏移(CLS),浏览器会回调最新值。CLS 需要累加 value 属性(未在有用户输入偏移后的事件中记录)。
2. 自定义性能埋点:封装 PerformanceMonitor 类
除了浏览器原生指标,还需要 DNS、TCP连接、DOM解析及页面完全加载耗时。通过 performance.timing 计算:
- 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();
复制代码
3. 错误监控:全局捕获运行时与 Promise 异常
通过 window.addEventListener('error', ... , true) 捕获未被 try/catch 的 JS 错误;通过 unhandledrejection 监听未处理的 Promise 拒绝。
- 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()
- });
- });
复制代码
4. 框架级错误捕获:Vue 和 React
Vue 3 通过 app.config.errorHandler 全局钩子捕获组件渲染错误;React 16+ 使用 ErrorBoundary 类组件和 getDerivedStateFromError/componentDidCatch。
- // Vue 3
- 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 ErrorBoundary
- 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;
- }
- }
复制代码
5. 资源加载与 HTTP 请求错误
通过全局 error 事件检测图片加载失败,通过重写 fetch 方法捕获 HTTP 非 2xx 响应或网络异常。
- 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;
- }
- };
复制代码
6. 行为监控:用户点击、滚动、页面停留
使用 BehaviorTracker 类记录交互行为:点击元素信息、页面滚动深度(防抖)、页面浏览时长(beforeunload 时上报)。利用 navigator.sendBeacon 保证页面关闭时数据不丢。
- 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);
- });
复制代码
7. 性能与行为关联分析
通过 Analytics 类将用户操作与性能数据关联,例如统计某类操作后的平均响应时间和错误率。
- 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
- };
- }
- }
复制代码
8. 统一上报与告警
MonitorService 封装批量上报逻辑,使用 sendBeacon 或 fetch 定时 flush,支持 error 优先即时上报。告警规则按指标阈值触发通知。
- 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()
- });
- }
- }
复制代码
总结:上述代码覆盖了性能(FCP/LCP/CLS及自定义埋点)、错误(runtime/promise/资源/框架)、行为(点击/滚动/页面停留)的采集与上报。建议在项目中集成 MonitorService 并设置合理的告警阈值,从而主动发现性能劣化与异常。监控的本质是预防问题,而非事后补救。 |