如何编写全局捕获异步异常信息脚本

wen 实用脚本 30

本文目录导读:

如何编写全局捕获异步异常信息脚本

  1. 浏览器环境 - 全局错误捕获
  2. Node.js 环境 - 全局异步错误捕获
  3. 通用异步错误封装
  4. 完整的生产环境示例
  5. 使用建议

我来帮你编写一个全局捕获异步异常信息的脚本,这里提供几种常见场景的实现方案:

浏览器环境 - 全局错误捕获

// global-error-handler.js
class GlobalErrorHandler {
  constructor() {
    this.init();
  }
  init() {
    // 捕获未处理的Promise异常
    window.addEventListener('unhandledrejection', (event) => {
      console.error('未处理的Promise拒绝:', {
        reason: event.reason,
        promise: event.promise,
        time: new Date().toISOString()
      });
      // 记录错误信息
      this.logError({
        type: 'unhandledrejection',
        message: event.reason?.message || String(event.reason),
        stack: event.reason?.stack,
        timestamp: new Date().toISOString()
      });
      // 阻止默认处理
      event.preventDefault();
    });
    // 捕获全局错误
    window.addEventListener('error', (event) => {
      // 过滤资源加载错误
      if (event.target && (event.target.tagName || event.target.src)) {
        return;
      }
      console.error('全局错误:', {
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        error: event.error,
        time: new Date().toISOString()
      });
      this.logError({
        type: 'global_error',
        message: event.message,
        stack: event.error?.stack,
        filename: event.filename,
        line: event.lineno,
        column: event.colno,
        timestamp: new Date().toISOString()
      });
      event.preventDefault();
    });
    // 重写console.error以便控制台错误
    const originalConsoleError = console.error;
    console.error = (...args) => {
      this.logError({
        type: 'console_error',
        message: args.map(arg => String(arg)).join(' '),
        timestamp: new Date().toISOString()
      });
      originalConsoleError.apply(console, args);
    };
  }
  logError(errorInfo) {
    // 这里可以发送到服务器或存储到本地
    console.log('错误日志:', errorInfo);
    // 示例:发送到服务器
    // this.sendToServer(errorInfo);
    // 示例:存储到localStorage
    // this.saveToLocal(errorInfo);
  }
  sendToServer(errorInfo) {
    fetch('/api/log-error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorInfo)
    }).catch(e => console.error('日志发送失败:', e));
  }
  saveToLocal(errorInfo) {
    const errors = JSON.parse(localStorage.getItem('error_logs') || '[]');
    errors.push(errorInfo);
    // 只保留最近100条
    if (errors.length > 100) errors.shift();
    localStorage.setItem('error_logs', JSON.stringify(errors));
  }
}
// 使用
const errorHandler = new GlobalErrorHandler();

Node.js 环境 - 全局异步错误捕获

// node-global-error-handler.js
class NodeGlobalErrorHandler {
  constructor(options = {}) {
    this.options = {
      exitOnError: false,
      logToFile: true,
      logPath: './error.log',
      ...options
    };
    this.init();
  }
  init() {
    // 捕获未处理的Promise异常
    process.on('unhandledRejection', (reason, promise) => {
      console.error('未处理的Promise拒绝:', {
        reason: reason instanceof Error ? reason.message : reason,
        stack: reason instanceof Error ? reason.stack : new Error().stack,
        time: new Date().toISOString()
      });
      this.logError({
        type: 'UNHANDLED_REJECTION',
        message: reason instanceof Error ? reason.message : String(reason),
        stack: reason instanceof Error ? reason.stack : undefined,
        timestamp: new Date().toISOString()
      });
      // 可选择优雅退出
      if (this.options.exitOnError) {
        process.exit(1);
      }
    });
    // 捕获未捕获的异常
    process.on('uncaughtException', (error) => {
      console.error('未捕获的异常:', {
        message: error.message,
        stack: error.stack,
        time: new Date().toISOString()
      });
      this.logError({
        type: 'UNCAUGHT_EXCEPTION',
        message: error.message,
        stack: error.stack,
        timestamp: new Date().toISOString()
      });
      if (this.options.exitOnError) {
        process.exit(1);
      }
    });
    // 监听警告
    process.on('warning', (warning) => {
      console.warn('警告:', warning.message);
      this.logError({
        type: 'WARNING',
        message: warning.message,
        stack: warning.stack,
        timestamp: new Date().toISOString()
      });
    });
  }
  logError(errorInfo) {
    const logEntry = JSON.stringify(errorInfo) + '\n';
    if (this.options.logToFile) {
      const fs = require('fs');
      fs.appendFile(this.options.logPath, logEntry, (err) => {
        if (err) console.error('写入日志文件失败:', err);
      });
    }
    // 可以添加其他日志存储方式
    console.log('错误日志:', errorInfo);
  }
}
// 使用
const errorHandler = new NodeGlobalErrorHandler({
  exitOnError: false,
  logPath: './errors.log'
});

通用异步错误封装

// async-error-wrapper.js
class AsyncErrorWrapper {
  // 包装异步函数
  static wrapAsync(fn) {
    return async (...args) => {
      try {
        return await fn(...args);
      } catch (error) {
        this.handleError(error, { functionName: fn.name, args });
        throw error; // 重新抛出或返回默认值
      }
    };
  }
  // 包装Promise
  static wrapPromise(promise, errorHandler = null) {
    return promise.catch(error => {
      if (errorHandler) {
        return errorHandler(error);
      }
      this.handleError(error);
      return null; // 返回默认值
    });
  }
  // 处理错误
  static handleError(error, context = {}) {
    console.error('异步错误捕获:', {
      message: error.message,
      stack: error.stack,
      time: new Date().toISOString(),
      ...context
    });
    // 可以添加自定义错误处理逻辑
    // this.sendToServer({ ... });
  }
  // 重试机制
  static async retry(fn, maxRetries = 3, delay = 1000) {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn();
      } catch (error) {
        console.warn(`第 ${i + 1} 次重试失败:`, error.message);
        if (i === maxRetries - 1) {
          throw error;
        }
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
}
// 使用示例
const safeAsyncFunction = AsyncErrorWrapper.wrapAsync(async () => {
  const response = await fetch('https://api.example.com/data');
  return response.json();
});
// 或者包装Promise
const safePromise = AsyncErrorWrapper.wrapPromise(
  fetch('https://api.example.com/data'),
  (error) => {
    console.log('友好的错误处理:', error.message);
    return { error: true, message: '请求失败' };
  }
);
// 带重试的调用
AsyncErrorWrapper.retry(
  async () => fetch('https://api.example.com/data'),
  3,
  2000
);

完整的生产环境示例

// production-error-handler.js
class ProductionErrorHandler {
  constructor(config = {}) {
    this.config = {
      logLevel: process.env.NODE_ENV === 'production' ? 'error' : 'warn',
      sampleRate: 1, // 采样率
      maxErrors: 100,
      ...config
    };
    this.errorCount = 0;
    this.errors = [];
    this.init();
  }
  init() {
    this.setupGlobalHandlers();
    this.setupFrameworkIntegration();
  }
  setupGlobalHandlers() {
    // 浏览器
    if (typeof window !== 'undefined') {
      this.setupBrowserHandlers();
    }
    // Node.js
    else {
      this.setupNodeHandlers();
    }
  }
  setupBrowserHandlers() {
    window.onerror = (message, source, lineno, colno, error) => {
      this.captureError('WINDOW_ERROR', {
        message,
        source,
        lineno,
        colno,
        stack: error?.stack
      });
    };
    window.onunhandledrejection = (event) => {
      this.captureError('UNHANDLED_REJECTION', {
        message: event.reason?.message || String(event.reason),
        stack: event.reason?.stack
      });
    };
  }
  setupNodeHandlers() {
    process.on('uncaughtException', (error) => {
      this.captureError('UNCAUGHT_EXCEPTION', {
        message: error.message,
        stack: error.stack
      });
      process.exit(1);
    });
    process.on('unhandledRejection', (reason) => {
      this.captureError('UNHANDLED_REJECTION', {
        message: reason instanceof Error ? reason.message : String(reason),
        stack: reason instanceof Error ? reason.stack : undefined
      });
    });
  }
  captureError(type, data) {
    if (this.errorCount >= this.config.maxErrors) return;
    if (Math.random() > this.config.sampleRate) return;
    const errorEntry = {
      type,
      timestamp: new Date().toISOString(),
      url: typeof window !== 'undefined' ? window.location.href : process.cwd(),
      userAgent: navigator?.userAgent,
      ...data
    };
    this.errors.push(errorEntry);
    this.errorCount++;
    this.sendToAnalytics(errorEntry);
    this.logToConsole(errorEntry);
  }
  sendToAnalytics(errorEntry) {
    // 发送到错误监控服务
    fetch('/api/error-tracking', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorEntry)
    }).catch(() => {});
  }
  logToConsole(entry) {
    if (this.config.logLevel === 'error') {
      console.error('[ErrorHandler]', entry.type, entry.message);
    } else {
      console.warn('[ErrorHandler]', entry);
    }
  }
  getErrors() {
    return this.errors;
  }
  clearErrors() {
    this.errors = [];
    this.errorCount = 0;
  }
}
// 使用
const errorHandler = new ProductionErrorHandler({
  logLevel: 'error',
  sampleRate: 0.5,
  maxErrors: 1000
});

使用建议

  1. 不同环境选择不同方案

    • 浏览器端:使用 window.onerrorunhandledrejection
    • Node.js:使用 process.on 事件监听
  2. 错误分级处理

    • 致命错误:立即处理和报警
    • 一般错误:记录日志
    • 警告信息:只记录不报警
  3. 性能优化

    • 使用采样率减少性能影响
    • 限制错误数量避免内存泄漏
    • 异步发送错误日志
  4. 安全考虑

    • 不要直接暴露错误堆栈给用户
    • 敏感信息脱敏处理
    • 设置合理的日志级别
  5. 集成方案

    • 结合 Sentry、TrackJS 等服务
    • 自定义日志服务器
    • 本地日志文件管理

这些脚本可以根据你的具体需求进行调整和扩展。

抱歉,评论功能暂时关闭!