javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > 捕获并上报JS错误与白屏

前端异常监控之如何捕获并上报JS错误与白屏详解

作者:阳咚

在前端开发中,错误处理是提升用户体验的关键环节,当用户在使用应用时遇到错误,一个友好的提示能有效降低用户的挫败感,这篇文章主要介绍了前端异常监控之如何捕获并上报JS错误与白屏的相关资料,需要的朋友可以参考下

引言

在现代前端开发中,用户体验是衡量产品成功与否的关键指标。然而,前端应用运行在复杂多变的环境中,浏览器差异、网络问题、设备性能等因素都可能导致各种异常情况的发生。如何及时发现并解决这些问题,成为前端工程师面临的重要挑战。

本文将深入探讨前端异常监控的核心技术,包括JS错误捕获、白屏监控以及错误上报机制,帮助开发者构建更加稳定可靠的前端应用。

一、JS错误捕获技术

1.1 try-catch 语句

最基础的错误捕获方式是使用 try-catch 语句,它可以捕获代码块中同步执行的错误:

/**
 * 捕获同步代码错误
 * @param {Function} fn - 要执行的函数
 * @param {Function} fallback - 错误处理函数
 * @returns {any} 函数执行结果
 */
function safeExecute(fn, fallback) {
  try {
    return fn();
  } catch (error) {
    console.error('执行出错:', error);
    return fallback ? fallback(error) : null;
  }
}

// 使用示例
safeExecute(
  () => {
    const result = 10 / 0; // 会导致错误
    return result;
  },
  (error) => {
    console.log('捕获到错误,执行备用方案');
    return 0;
  }
);

局限性try-catch 只能捕获同步错误,无法捕获异步错误和Promise中的错误。

1.2 window.onerror 事件

window.onerror 可以捕获全局范围内的JS错误,包括异步错误:

/**
 * 全局错误捕获
 */
function setupGlobalErrorHandler() {
  window.onerror = function(message, source, lineno, colno, error) {
    console.error('全局错误:', {
      message,
      source,
      lineno,
      colno,
      error
    });
    // 这里可以添加错误上报逻辑
    return true; // 阻止默认处理
  };
}

// 启用全局错误捕获
setupGlobalErrorHandler();

注意事项

1.3 Promise 错误捕获

Promise 中的错误需要使用 catch 方法或 unhandledrejection 事件来捕获:

/**
 * Promise 错误捕获
 */
function setupPromiseErrorHandler() {
  window.addEventListener('unhandledrejection', function(event) {
    console.error('未处理的Promise错误:', {
      reason: event.reason,
      promise: event.promise
    });
    // 这里可以添加错误上报逻辑
    event.preventDefault(); // 阻止默认处理
  });
}

// 启用Promise错误捕获
setupPromiseErrorHandler();

1.4 Vue 应用中的错误捕获

在 Vue 应用中,可以使用 errorHandler 来捕获组件渲染和观察期间的错误:

import Vue from 'vue';

/**
 * Vue 错误捕获
 */
Vue.config.errorHandler = function(err, vm, info) {
  console.error('Vue 错误:', {
    error: err,
    component: vm ? vm.$options.name || 'Anonymous Component' : 'Unknown',
    info // 错误发生的位置信息
  });
  // 这里可以添加错误上报逻辑
};

对于 Vue 3,可以使用 app.config.errorHandler

import { createApp } from 'vue';

const app = createApp(App);

app.config.errorHandler = (err, instance, info) => {
  console.error('Vue 3 错误:', {
    error: err,
    instance,
    info
  });
  // 这里可以添加错误上报逻辑
};

app.mount('#app');

二、白屏监控技术

白屏是前端应用中另一个严重影响用户体验的问题。白屏监控主要有以下几种方案:

2.1 基于DOM元素的监控

通过检查关键DOM元素是否存在来判断是否发生白屏:

/**
 * 白屏监控 - 基于DOM元素
 * @param {string} selector - 关键DOM元素选择器
 * @param {number} timeout - 超时时间(ms)
 * @param {Function} callback - 白屏回调
 */
function monitorWhiteScreen(selector, timeout = 3000, callback) {
  const checkTime = Date.now();
  
  function checkElement() {
    const element = document.querySelector(selector);
    if (element) {
      console.log('页面正常加载');
    } else {
      if (Date.now() - checkTime > timeout) {
        console.error('检测到白屏');
        if (callback) {
          callback();
        }
      } else {
        requestAnimationFrame(checkElement);
      }
    }
  }
  
  requestAnimationFrame(checkElement);
}

// 使用示例
monitorWhiteScreen('#app', 5000, () => {
  // 白屏处理逻辑
  console.log('上报白屏事件');
});

2.2 基于视觉的监控

通过检查页面的可见内容来判断是否发生白屏:

/**
 * 白屏监控 - 基于视觉
 * @param {number} threshold - 阈值(0-1)
 * @param {number} timeout - 超时时间(ms)
 * @param {Function} callback - 白屏回调
 */
function monitorVisualWhiteScreen(threshold = 0.8, timeout = 3000, callback) {
  const checkTime = Date.now();
  
  function checkVisual() {
    if (Date.now() - checkTime > timeout) {
      const body = document.body;
      const rect = body.getBoundingClientRect();
      const area = rect.width * rect.height;
      
      if (area === 0) {
        console.error('检测到白屏:body面积为0');
        if (callback) callback();
        return;
      }
      
      // 这里可以添加更复杂的视觉检测逻辑
      // 例如使用Canvas截图分析像素
      
      console.log('页面视觉检查通过');
    } else {
      requestAnimationFrame(checkVisual);
    }
  }
  
  requestAnimationFrame(checkVisual);
}

2.3 基于性能指标的监控

利用Performance API监控页面加载性能,间接判断白屏情况:

/**
 * 白屏监控 - 基于性能指标
 * @param {number} timeout - 超时时间(ms)
 * @param {Function} callback - 白屏回调
 */
function monitorPerformanceWhiteScreen(timeout = 5000, callback) {
  const checkTime = Date.now();
  
  function checkPerformance() {
    if (Date.now() - checkTime > timeout) {
      const navigation = performance.getEntriesByType('navigation')[0];
      if (navigation) {
        console.log('页面性能指标:', {
          domContentLoadedEventEnd: navigation.domContentLoadedEventEnd,
          loadEventEnd: navigation.loadEventEnd
        });
        
        // 如果关键时间点为0,可能发生了白屏
        if (navigation.domContentLoadedEventEnd === 0) {
          console.error('检测到白屏:DOM未加载完成');
          if (callback) callback();
        }
      }
    } else {
      requestAnimationFrame(checkPerformance);
    }
  }
  
  requestAnimationFrame(checkPerformance);
}

三、错误上报机制

捕获错误只是第一步,更重要的是将错误信息上报到服务器,以便开发团队及时发现和解决问题。

3.1 错误上报数据结构

一个完整的错误上报数据结构应该包含以下信息:

/**
 * 构建错误上报数据
 * @param {Error} error - 错误对象
 * @param {string} type - 错误类型
 * @returns {Object} 错误上报数据
 */
function buildErrorReport(error, type = 'js') {
  return {
    // 错误基本信息
    type,
    message: error.message || '未知错误',
    stack: error.stack || '',
    
    // 上下文信息
    url: window.location.href,
    userAgent: navigator.userAgent,
    timestamp: Date.now(),
    
    // 设备信息
    device: {
      screenWidth: window.screen.width,
      screenHeight: window.screen.height,
      language: navigator.language
    },
    
    // 网络信息
    network: navigator.connection ? {
      type: navigator.connection.type,
      effectiveType: navigator.connection.effectiveType,
      rtt: navigator.connection.rtt
    } : null,
    
    // 用户信息(可选,需注意隐私)
    userId: getUserId() // 假设有一个获取用户ID的函数
  };
}

3.2 错误上报策略

为了平衡实时性和性能,需要设计合理的错误上报策略:

/**
 * 错误上报管理器
 */
class ErrorReporter {
  constructor() {
    this.queue = [];
    this.batchSize = 10; // 批量上报大小
    this.debounceTime = 1000; // 防抖时间(ms)
    this.debounceTimer = null;
    this.apiUrl = '/api/error-report'; // 上报接口
  }
  
  /**
   * 上报错误
   * @param {Object} errorData - 错误数据
   */
  report(errorData) {
    this.queue.push(errorData);
    this.scheduleReport();
  }
  
  /**
   * 调度上报
   */
  scheduleReport() {
    if (this.queue.length >= this.batchSize) {
      this.flush();
      return;
    }
    
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
    
    this.debounceTimer = setTimeout(() => {
      this.flush();
    }, this.debounceTime);
  }
  
  /**
   * 执行上报
   */
  async flush() {
    if (this.queue.length === 0) return;
    
    const errors = [...this.queue];
    this.queue = [];
    
    try {
      const response = await fetch(this.apiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ errors })
      });
      
      if (!response.ok) {
        console.error('错误上报失败:', response.status);
        // 失败后可以重试或存储到本地
        this.queue.unshift(...errors);
      } else {
        console.log('错误上报成功');
      }
    } catch (error) {
      console.error('上报网络错误:', error);
      // 网络错误时存储到本地
      this.queue.unshift(...errors);
    }
  }
}

// 创建错误上报实例
const errorReporter = new ErrorReporter();

3.3 本地存储与重试机制

为了应对网络不稳定的情况,可以添加本地存储和重试机制:

/**
 * 增强版错误上报管理器
 */
class EnhancedErrorReporter extends ErrorReporter {
  constructor() {
    super();
    this.storageKey = 'error-reports';
    this.loadFromStorage();
  }
  
  /**
   * 从本地存储加载错误
   */
  loadFromStorage() {
    try {
      const stored = localStorage.getItem(this.storageKey);
      if (stored) {
        const errors = JSON.parse(stored);
        this.queue.push(...errors);
        localStorage.removeItem(this.storageKey);
        console.log('从本地存储恢复错误报告:', errors.length);
      }
    } catch (error) {
      console.error('加载本地错误报告失败:', error);
    }
  }
  
  /**
   * 保存错误到本地存储
   */
  saveToStorage() {
    try {
      if (this.queue.length > 0) {
        localStorage.setItem(this.storageKey, JSON.stringify(this.queue));
        console.log('错误报告已保存到本地存储');
      }
    } catch (error) {
      console.error('保存错误报告到本地存储失败:', error);
    }
  }
  
  /**
   * 执行上报
   */
  async flush() {
    if (this.queue.length === 0) return;
    
    const errors = [...this.queue];
    
    try {
      // 调用父类的flush方法
      await super.flush();
    } finally {
      // 如果还有未上报的错误,保存到本地
      if (this.queue.length > 0) {
        this.saveToStorage();
      }
    }
  }
}

四、综合监控方案

将上述技术整合起来,构建一个完整的前端异常监控系统:

4.1 监控系统架构

/**
 * 前端监控系统
 */
class FrontendMonitor {
  constructor(options = {}) {
    this.options = {
      errorReportUrl: '/api/error-report',
      whiteScreenSelector: '#app',
      whiteScreenTimeout: 5000,
      ...options
    };
    
    this.errorReporter = new EnhancedErrorReporter();
    this.errorReporter.apiUrl = this.options.errorReportUrl;
    
    this.setupErrorHandlers();
    this.setupWhiteScreenMonitor();
  }
  
  /**
   * 设置错误处理器
   */
  setupErrorHandlers() {
    // 全局错误处理
    window.onerror = (message, source, lineno, colno, error) => {
      const errorData = buildErrorReport(error || new Error(message), 'js');
      errorData.detail = {
        source,
        lineno,
        colno
      };
      this.errorReporter.report(errorData);
      return true;
    };
    
    // Promise错误处理
    window.addEventListener('unhandledrejection', (event) => {
      const errorData = buildErrorReport(event.reason, 'promise');
      this.errorReporter.report(errorData);
      event.preventDefault();
    });
    
    // 资源加载错误
    window.addEventListener('error', (event) => {
      if (event.target instanceof Element) {
        const errorData = {
          type: 'resource',
          message: `资源加载失败: ${event.target.src || event.target.href}`,
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: Date.now(),
          detail: {
            tagName: event.target.tagName,
            src: event.target.src,
            href: event.target.href
          }
        };
        this.errorReporter.report(errorData);
      }
    }, true);
  }
  
  /**
   * 设置白屏监控
   */
  setupWhiteScreenMonitor() {
    monitorWhiteScreen(
      this.options.whiteScreenSelector,
      this.options.whiteScreenTimeout,
      () => {
        const errorData = {
          type: 'white-screen',
          message: '页面白屏',
          url: window.location.href,
          userAgent: navigator.userAgent,
          timestamp: Date.now(),
          detail: {
            selector: this.options.whiteScreenSelector,
            timeout: this.options.whiteScreenTimeout
          }
        };
        this.errorReporter.report(errorData);
      }
    );
  }
  
  /**
   * 手动上报错误
   * @param {Error|string} error - 错误对象或错误信息
   * @param {Object} context - 上下文信息
   */
  reportError(error, context = {}) {
    const errorObj = typeof error === 'string' ? new Error(error) : error;
    const errorData = buildErrorReport(errorObj, 'custom');
    errorData.context = context;
    this.errorReporter.report(errorData);
  }
  
  /**
   * 上报性能指标
   * @param {Object} metrics - 性能指标
   */
  reportPerformance(metrics) {
    const performanceData = {
      type: 'performance',
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: Date.now(),
      metrics
    };
    this.errorReporter.report(performanceData);
  }
}

4.2 使用示例

// 初始化监控系统
const monitor = new FrontendMonitor({
  errorReportUrl: 'https://monitor.example.com/api/report',
  whiteScreenSelector: '.main-content',
  whiteScreenTimeout: 8000
});

// 手动上报错误
function riskyOperation() {
  try {
    // 可能出错的操作
  } catch (error) {
    monitor.reportError(error, {
      operation: 'riskyOperation',
      params: { /* 相关参数 */ }
    });
  }
}

// 上报性能指标
function reportPagePerformance() {
  const navigation = performance.getEntriesByType('navigation')[0];
  if (navigation) {
    monitor.reportPerformance({
      domContentLoaded: navigation.domContentLoadedEventEnd - navigation.startTime,
      loadTime: navigation.loadEventEnd - navigation.startTime,
      firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime || 0,
      firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime || 0
    });
  }
}

// 页面加载完成后上报性能
window.addEventListener('load', reportPagePerformance);

五、实践建议与最佳实践

5.1 错误监控最佳实践

  1. 分级监控:根据错误的严重程度进行分级,优先处理影响用户体验的严重错误
  2. 上下文丰富:上报错误时附带足够的上下文信息,如用户操作路径、设备信息等
  3. 采样率控制:对于高频错误,设置合理的采样率,避免上报数据过多
  4. 版本管理:在错误上报中包含应用版本信息,便于定位问题
  5. 测试环境:在测试环境中验证监控系统的有效性

5.2 白屏监控最佳实践

  1. 多维度监控:结合DOM元素检查、视觉检查和性能指标进行综合判断
  2. 阈值调优:根据不同页面的加载特性,调整合理的白屏检测阈值
  3. 排除干扰:对于首屏确实为空的页面,需要设置排除规则
  4. 用户反馈:提供用户主动反馈白屏的渠道

5.3 性能优化建议

  1. 代码分割:减少首屏加载时间,降低白屏概率
  2. 资源预加载:对于关键资源,使用预加载技术
  3. 错误边界:在React/Vue等框架中使用错误边界,防止单个组件错误导致整个应用崩溃
  4. 离线缓存:使用Service Worker缓存静态资源,提高页面加载可靠性

六、实际应用案例

6.1 案例一:电商网站异常监控

背景:某电商网站在大促期间频繁出现白屏和JS错误,严重影响用户购物体验。

解决方案

  1. 部署前端监控系统,实时捕获错误和白屏事件
  2. 针对高频错误进行优先级排序,快速修复
  3. 优化首屏加载逻辑,减少白屏时间
  4. 建立错误预警机制,当错误率超过阈值时及时告警

效果

6.2 案例二:单页应用性能监控

背景:某单页应用在复杂操作时经常出现卡顿和错误,用户满意度低。

解决方案

  1. 集成前端监控系统,捕获运行时错误
  2. 监控关键操作的性能指标,如页面切换时间、API响应时间
  3. 分析错误分布,发现并修复内存泄漏问题
  4. 优化路由懒加载策略,减少初始加载时间

效果

七、未来发展趋势

7.1 智能化监控

随着AI技术的发展,前端监控系统将向智能化方向演进:

  1. 错误聚类:自动对错误进行聚类分析,识别相似错误模式
  2. 根因分析:利用机器学习算法自动分析错误根因
  3. 预测性监控:基于历史数据预测可能出现的问题
  4. 自适应阈值:根据应用状态自动调整监控阈值

7.2 全链路监控

前端监控将与后端监控、用户行为分析等系统深度融合,构建全链路监控体系:

  1. 用户旅程追踪:从用户点击到页面渲染的完整链路监控
  2. 端到端性能监控:前端性能与后端API性能的关联分析
  3. 跨设备监控:统一监控不同设备、不同浏览器的表现
  4. 实时可视化:提供直观的监控数据可视化界面

7.3 隐私保护

在监控系统发展的同时,用户隐私保护也将成为重要考量:

  1. 数据脱敏:对敏感信息进行自动脱敏处理
  2. 合规性:符合GDPR、CCPA等隐私法规要求
  3. 用户可控:提供用户选择退出监控的机制
  4. 数据最小化:只收集必要的监控数据

八、总结

前端异常监控是构建稳定可靠前端应用的重要保障,它不仅可以帮助开发者及时发现和解决问题,还可以为产品优化提供数据支撑。本文介绍的JS错误捕获、白屏监控和错误上报技术,为构建完整的前端监控系统提供了技术基础。

在实际应用中,需要根据项目的具体情况,选择合适的监控策略和技术方案。同时,要注重监控系统本身的性能和可靠性,避免监控代码成为新的性能瓶颈。

随着前端技术的不断发展,前端监控系统也在不断演进。未来,智能化、全链路和隐私保护将成为前端监控的重要发展方向。作为前端开发者,我们需要持续关注这些变化,不断优化监控策略,为用户提供更加稳定、流畅的前端体验。

结语

前端异常监控是一项需要持续投入和优化的工作,它不仅是技术问题,更是产品质量和用户体验的重要组成部分。通过本文介绍的技术和实践,希望能够帮助开发者构建更加完善的前端监控系统,让前端应用更加稳定可靠,为用户创造更好的使用体验。

记住,一个好的监控系统不仅能够捕获错误,更能够帮助我们理解用户的真实使用场景,为产品的持续优化提供数据驱动的决策依据。让我们一起努力,构建更加健康的前端生态系统。

到此这篇关于前端异常监控之如何捕获并上报JS错误与白屏的文章就介绍到这了,更多相关捕获并上报JS错误与白屏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文