企业级Electron应用异常监控平台搭建指南:从崩溃捕获到问题定位

企业级Electron应用异常监控平台搭建指南:从崩溃捕获到问题定位

【免费下载链接】electron-egg A simple, cross platform, enterprise desktop software development framework 【免费下载链接】electron-egg 项目地址: https://gitcode.com/dromara/electron-egg

引言

在企业级桌面应用开发中,异常监控是保障软件稳定性的关键环节。Electron作为跨平台桌面应用开发框架,其主进程与渲染进程分离的架构带来了独特的异常处理挑战。本文将详细介绍如何基于dromara/electron-egg框架构建完整的应用异常监控平台,涵盖日志系统配置、多进程异常捕获、崩溃报告生成和可视化监控面板实现等核心功能。

异常监控体系架构

异常监控平台需要覆盖Electron应用的全生命周期,包括主进程、渲染进程和预加载脚本的异常捕获。基于electron-egg框架的监控体系架构如下:

mermaid

electron-egg框架已内置基础日志功能,通过electron/config/config.default.js配置文件可自定义日志行为。默认日志配置如下:

logger: {
  level: 'INFO',
  outputJSON: false,
  appLogName: 'ee.log',
  coreLogName: 'ee-core.log',
  errorLogName: 'ee-error.log' 
}

日志系统配置与优化

基础日志配置

electron-egg的日志系统由electron/config/config.default.js文件控制,默认将不同类型日志输出到独立文件:

  • 应用日志:ee.log - 记录应用运行信息
  • 核心日志:ee-core.log - 框架核心组件日志
  • 错误日志:ee-error.log - 集中存储异常信息

要启用详细异常记录,需将日志级别调整为DEBUG

// 修改配置文件 [electron/config/config.default.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/electron/config/config.default.js?utm_source=gitcode_repo_files)
logger: {
  level: 'DEBUG',  // 由INFO改为DEBUG
  outputJSON: true, // 启用JSON格式便于日志解析
  // 其他配置保持不变
}

自定义日志工具

创建专用异常记录工具,统一异常日志格式:

// 创建文件 electron/utils/errorMonitor.js
const { logger } = require('ee-core/log');
const fs = require('fs');
const path = require('path');
const { app } = require('electron');

class ErrorMonitor {
  constructor() {
    this.errorLogPath = path.join(app.getPath('userData'), 'logs', 'ee-error.log');
  }

  /**
   * 记录异常信息
   * @param {Error} error - 错误对象
   * @param {string} context - 错误发生上下文
   */
  captureException(error, context = '') {
    const errorInfo = {
      timestamp: new Date().toISOString(),
      message: error.message,
      stack: error.stack,
      context,
      process: process.type, // 区分主进程/渲染进程
      version: app.getVersion()
    };
    
    // 写入JSON格式日志
    logger.error(JSON.stringify(errorInfo));
    
    // 同时记录到专用错误文件
    this.writeErrorReport(errorInfo);
  }

  /**
   * 生成崩溃报告
   */
  writeErrorReport(errorInfo) {
    const reportPath = path.join(app.getPath('userData'), 'crash-reports', 
      `report-${Date.now()}.json`);
    
    // 确保目录存在
    fs.mkdirSync(path.dirname(reportPath), { recursive: true });
    
    // 写入报告文件
    fs.writeFileSync(reportPath, JSON.stringify(errorInfo, null, 2));
  }
}

module.exports = new ErrorMonitor();

多进程异常捕获实现

主进程异常捕获

Electron主进程异常包括未捕获的异常、未处理的Promise拒绝和进程崩溃事件。修改electron/main.js文件添加异常捕获逻辑:

// 修改 [electron/main.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/electron/main.js?utm_source=gitcode_repo_files)
const { app, BrowserWindow } = require('electron');
const errorMonitor = require('./utils/errorMonitor');
const { logger } = require('ee-core/log');

// 捕获未处理的异常
process.on('uncaughtException', (error) => {
  logger.error('Main process uncaught exception:', error);
  errorMonitor.captureException(error, '主进程未捕获异常');
  
  // 可选:尝试重启应用
  // app.relaunch();
  // app.quit();
});

// 捕获未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
  logger.error('Main process unhandled rejection:', reason);
  errorMonitor.captureException(reason, '主进程Promise拒绝');
});

// 窗口崩溃事件
app.on('render-process-gone', (event, webContents, details) => {
  logger.error(`Render process gone: ${details.reason}`);
  errorMonitor.captureException(
    new Error(`渲染进程崩溃: ${details.reason}`), 
    `进程ID: ${webContents.id}, 原因: ${details.reason}`
  );
  
  // 重启窗口
  if (details.reason === 'crashed') {
    // 实现窗口重启逻辑
  }
});

渲染进程异常捕获

前端Vue应用的异常需要通过预加载脚本建立与主进程的通信通道。修改electron/preload/bridge.js添加异常捕获API:

// 修改 [electron/preload/bridge.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/electron/preload/bridge.js?utm_source=gitcode_repo_files)
const { contextBridge, ipcRenderer } = require('electron');
const { logger } = require('ee-core/log');

// 暴露异常捕获API给渲染进程
contextBridge.exposeInMainWorld('errorMonitor', {
  captureException: (error, context) => {
    logger.error(`Render process error: ${error.message}`);
    ipcRenderer.send('renderer-error', {
      message: error.message,
      stack: error.stack,
      context
    });
  }
});

// 捕获window错误
window.addEventListener('error', (event) => {
  event.preventDefault();
  const error = {
    message: event.error.message,
    stack: event.error.stack,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno
  };
  
  logger.error(`Window error: ${JSON.stringify(error)}`);
  ipcRenderer.send('renderer-error', {
    ...error,
    context: 'window.onerror'
  });
});

// 捕获未处理的Promise拒绝
window.addEventListener('unhandledrejection', (event) => {
  event.preventDefault();
  const reason = event.reason;
  
  logger.error(`Unhandled rejection: ${reason.message || reason}`);
  ipcRenderer.send('renderer-error', {
    message: reason.message || reason.toString(),
    stack: reason.stack,
    context: 'unhandledrejection'
  });
});

预加载脚本异常处理

预加载脚本作为连接主进程和渲染进程的桥梁,其异常处理尤为重要。修改electron/preload/lifecycle.js添加try/catch块:

// 修改 [electron/preload/lifecycle.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/electron/preload/lifecycle.js?utm_source=gitcode_repo_files)
const { logger } = require('ee-core/log');
const errorMonitor = require('../utils/errorMonitor');

class Lifecycle {
  /**
   * core app have been loaded
   */
  async ready() {
    try {
      logger.info('[lifecycle] ready');
      // 原有逻辑...
    } catch (error) {
      logger.error('[lifecycle] ready error:', error);
      errorMonitor.captureException(error, 'lifecycle.ready');
    }
  }

  /**
   * electron app ready
   */
  async electronAppReady() {
    try {
      logger.info('[lifecycle] electron-app-ready');
      // 原有逻辑...
    } catch (error) {
      logger.error('[lifecycle] electronAppReady error:', error);
      errorMonitor.captureException(error, 'lifecycle.electronAppReady');
    }
  }
  
  // 其他方法类似添加try/catch...
}

module.exports = { Lifecycle };

异常监控面板实现

后端数据接口

创建异常监控控制器,提供日志数据API:

// 创建文件 electron/controller/monitorController.js
const { logger } = require('ee-core/log');
const fs = require('fs');
const path = require('path');
const { app } = require('electron');

class MonitorController {
  /**
   * 获取错误日志列表
   */
  async getErrorLogs() {
    try {
      const logDir = path.join(app.getPath('userData'), 'logs');
      const errorLogPath = path.join(logDir, 'ee-error.log');
      
      // 读取日志文件内容
      const content = fs.readFileSync(errorLogPath, 'utf-8');
      
      // 按行分割并解析JSON日志
      const logs = content.split('\n')
        .filter(line => line.trim())
        .map(line => {
          try {
            return JSON.parse(line);
          } catch (e) {
            // 处理非JSON格式日志
            return { message: line, timestamp: new Date().toISOString() };
          }
        })
        .reverse(); // 最新的日志在前
        
      return {
        success: true,
        data: logs.slice(0, 100) // 返回最近100条
      };
    } catch (error) {
      logger.error('Failed to get error logs:', error);
      return {
        success: false,
        message: error.message
      };
    }
  }
  
  /**
   * 获取崩溃报告列表
   */
  async getCrashReports() {
    try {
      const reportDir = path.join(app.getPath('userData'), 'crash-reports');
      
      // 确保目录存在
      if (!fs.existsSync(reportDir)) {
        return { success: true, data: [] };
      }
      
      // 读取报告文件列表
      const files = fs.readdirSync(reportDir)
        .filter(file => file.endsWith('.json'))
        .sort((a, b) => b.localeCompare(a)); // 按文件名倒序
      
      // 读取报告内容
      const reports = files.map(file => {
        const content = fs.readFileSync(path.join(reportDir, file), 'utf-8');
        return {
          filename: file,
          timestamp: file.replace('report-', '').replace('.json', ''),
          data: JSON.parse(content)
        };
      });
      
      return {
        success: true,
        data: reports
      };
    } catch (error) {
      logger.error('Failed to get crash reports:', error);
      return {
        success: false,
        message: error.message
      };
    }
  }
}

module.exports = MonitorController;

前端监控面板

创建Vue组件实现异常监控可视化界面:

<!-- 创建文件 frontend/src/views/monitor/ErrorMonitor.vue -->
<template>
  <div class="error-monitor-container">
    <div class="header">
      <h2>应用异常监控中心</h2>
      <div class="stats">
        <div class="stat-item">
          <span class="stat-value">{{ errorCount }}</span>
          <span class="stat-label">今日错误总数</span>
        </div>
        <div class="stat-item">
          <span class="stat-value">{{ crashCount }}</span>
          <span class="stat-label">崩溃次数</span>
        </div>
        <div class="stat-item">
          <span class="stat-value">{{ processType }}</span>
          <span class="stat-label">当前进程</span>
        </div>
      </div>
    </div>
    
    <div class="tabs">
      <div class="tab" :class="{ active: activeTab === 'errors' }" @click="activeTab = 'errors'">
        错误日志
      </div>
      <div class="tab" :class="{ active: activeTab === 'crashes' }" @click="activeTab = 'crashes'">
        崩溃报告
      </div>
      <div class="tab" :class="{ active: activeTab === 'settings' }" @click="activeTab = 'settings'">
        监控设置
      </div>
    </div>
    
    <div class="tab-content">
      <div v-if="activeTab === 'errors'">
        <div class="filter-bar">
          <input 
            type="text" 
            v-model="errorFilter" 
            placeholder="搜索错误信息..."
            class="search-input"
          >
          <select v-model="errorLevel" class="filter-select">
            <option value="all">所有级别</option>
            <option value="ERROR">错误</option>
            <option value="WARN">警告</option>
            <option value="DEBUG">调试</option>
          </select>
        </div>
        
        <div class="error-list">
          <div 
            class="error-item" 
            v-for="(error, index) in filteredErrors" 
            :key="index"
            :class="{ unread: !error.read }"
            @click="showErrorDetail(error)"
          >
            <div class="error-header">
              <span class="error-time">{{ formatTime(error.timestamp) }}</span>
              <span class="error-level" :class="`level-${error.level || 'ERROR'}`">
                {{ error.level || 'ERROR' }}
              </span>
            </div>
            <div class="error-message">{{ error.message }}</div>
            <div class="error-context">{{ error.context || '无上下文信息' }}</div>
          </div>
        </div>
      </div>
      
      <div v-if="activeTab === 'crashes'">
        <div class="crash-list">
          <div 
            class="crash-item" 
            v-for="(report, index) in crashReports" 
            :key="index"
            @click="showCrashDetail(report)"
          >
            <div class="crash-header">
              <span class="crash-time">{{ formatTimestamp(report.timestamp) }}</span>
              <span class="crash-process">{{ report.data.process || '未知进程' }}</span>
            </div>
            <div class="crash-message">{{ report.data.message }}</div>
            <div class="crash-version">应用版本: {{ report.data.version }}</div>
          </div>
        </div>
      </div>
      
      <div v-if="activeTab === 'settings'">
        <div class="settings-form">
          <div class="form-group">
            <label>日志级别</label>
            <select v-model="logLevel" class="form-control">
              <option value="DEBUG">DEBUG</option>
              <option value="INFO">INFO</option>
              <option value="WARN">WARN</option>
              <option value="ERROR">ERROR</option>
            </select>
          </div>
          
          <div class="form-group">
            <label>日志保留天数</label>
            <input type="number" v-model="logRetentionDays" class="form-control" min="1" max="365">
          </div>
          
          <div class="form-group">
            <label>自动发送错误报告</label>
            <input type="checkbox" v-model="autoSendReports">
          </div>
          
          <button class="btn-save" @click="saveSettings">保存设置</button>
        </div>
      </div>
    </div>
    
    <!-- 错误详情弹窗 -->
    <div class="modal" v-if="showErrorModal">
      <div class="modal-content">
        <div class="modal-header">
          <h3>错误详情</h3>
          <button class="close-btn" @click="showErrorModal = false">&times;</button>
        </div>
        <div class="modal-body">
          <pre>{{ selectedError | stringify }}</pre>
        </div>
        <div class="modal-footer">
          <button class="btn-close" @click="showErrorModal = false">关闭</button>
          <button class="btn-save" @click="exportErrorReport">导出报告</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeTab: 'errors',
      errorFilter: '',
      errorLevel: 'all',
      errorCount: 0,
      crashCount: 0,
      processType: 'renderer',
      errors: [],
      crashReports: [],
      showErrorModal: false,
      selectedError: null,
      logLevel: 'INFO',
      logRetentionDays: 30,
      autoSendReports: true
    };
  },
  
  computed: {
    filteredErrors() {
      return this.errors.filter(error => {
        const matchesFilter = error.message.toLowerCase().includes(this.errorFilter.toLowerCase()) ||
                             (error.stack && error.stack.toLowerCase().includes(this.errorFilter.toLowerCase()));
        
        const matchesLevel = this.errorLevel === 'all' || 
                            (error.level && error.level === this.errorLevel);
        
        return matchesFilter && matchesLevel;
      });
    }
  },
  
  filters: {
    stringify(value) {
      return JSON.stringify(value, null, 2);
    }
  },
  
  async mounted() {
    // 初始化时加载错误日志和崩溃报告
    await this.loadErrorLogs();
    await this.loadCrashReports();
    
    // 设置定时刷新
    this.refreshInterval = setInterval(async () => {
      await this.loadErrorLogs();
      await this.loadCrashReports();
    }, 30000); // 每30秒刷新一次
    
    // 获取当前进程类型
    this.processType = window.process && window.process.type || 'renderer';
    
    // 监听主进程发送的新错误事件
    window.ipcRenderer.on('new-error', (event, error) => {
      this.errors.unshift(error);
      this.errorCount++;
    });
  },
  
  beforeUnmount() {
    clearInterval(this.refreshInterval);
    window.ipcRenderer.removeAllListeners('new-error');
  },
  
  methods: {
    async loadErrorLogs() {
      try {
        const result = await window.api.invoke('monitorController.getErrorLogs');
        if (result.success) {
          this.errors = result.data;
          this.errorCount = result.data.length;
        }
      } catch (error) {
        console.error('Failed to load error logs:', error);
      }
    },
    
    async loadCrashReports() {
      try {
        const result = await window.api.invoke('monitorController.getCrashReports');
        if (result.success) {
          this.crashReports = result.data;
          this.crashCount = result.data.length;
        }
      } catch (error) {
        console.error('Failed to load crash reports:', error);
      }
    },
    
    formatTime(timestamp) {
      if (!timestamp) return '';
      const date = new Date(timestamp);
      return date.toLocaleString();
    },
    
    formatTimestamp(timestamp) {
      if (!timestamp) return '';
      const date = new Date(parseInt(timestamp));
      return date.toLocaleString();
    },
    
    showErrorDetail(error) {
      this.selectedError = error;
      this.showErrorModal = true;
      error.read = true;
    },
    
    async exportErrorReport() {
      try {
        await window.api.invoke('monitorController.exportErrorReport', this.selectedError);
        alert('错误报告已导出');
      } catch (error) {
        console.error('Failed to export error report:', error);
        alert('导出失败: ' + error.message);
      }
    },
    
    async saveSettings() {
      try {
        await window.api.invoke('monitorController.saveSettings', {
          logLevel: this.logLevel,
          logRetentionDays: this.logRetentionDays,
          autoSendReports: this.autoSendReports
        });
        alert('设置已保存');
      } catch (error) {
        console.error('Failed to save settings:', error);
        alert('保存失败: ' + error.message);
      }
    }
  }
};
</script>

<style scoped>
/* 样式内容省略 */
</style>

添加路由配置,使监控面板可访问:

// 修改 [frontend/src/router/routerMap.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/frontend/src/router/routerMap.js?utm_source=gitcode_repo_files)
export default [
  // 原有路由...
  {
    path: '/monitor/error',
    name: 'errorMonitor',
    component: () => import('@/views/monitor/ErrorMonitor.vue'),
    meta: {
      title: '异常监控中心'
    }
  }
];

异常监控工作流程

完整的异常监控工作流程包括异常捕获、日志记录、报告生成和问题修复四个阶段:

mermaid

当应用运行时,异常监控平台会自动捕获各类错误并展示在监控面板中:

异常监控面板

高级功能:远程错误上报

对于企业级应用,通常需要将错误报告发送到中央服务器进行集中分析。实现远程上报功能:

// 修改 electron/utils/errorMonitor.js
const { ipcRenderer } = require('electron');
const { getConfig } = require('ee-core/config');

class ErrorMonitor {
  // ... 原有代码 ...

  /**
   * 发送错误报告到远程服务器
   */
  async sendErrorToServer(errorInfo) {
    const config = getConfig();
    
    // 检查是否启用远程上报
    if (!config.remote.enable || !config.remote.url) {
      logger.info('Remote error reporting is not enabled');
      return false;
    }
    
    try {
      const response = await fetch(`${config.remote.url}/api/errors/report`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-App-Version': app.getVersion()
        },
        body: JSON.stringify(errorInfo),
        timeout: 5000
      });
      
      if (response.ok) {
        logger.info('Error report sent successfully');
        return true;
      } else {
        logger.error(`Failed to send error report: ${response.statusText}`);
        return false;
      }
    } catch (error) {
      logger.error('Network error while sending report:', error);
      return false;
    }
  }
}

module.exports = new ErrorMonitor();

配置远程上报服务器地址:

// 修改 [electron/config/config.default.js](https://gitcode.com/dromara/electron-egg/blob/3a1f5bc42c936f42ffcb693e327005e65ec13c3e/electron/config/config.default.js?utm_source=gitcode_repo_files)
remote: {
  enable: true, // 启用远程上报
  url: 'https://your-error-monitor-server.com' // 替换为实际服务器地址
}

总结与最佳实践

异常监控最佳实践

  1. 全面覆盖:确保监控所有进程(主进程、渲染进程、预加载脚本)
  2. 分级处理:根据错误严重程度采取不同措施(记录、弹窗、重启)
  3. 详细日志:异常日志应包含时间戳、上下文、堆栈信息和版本号
  4. 用户体验:避免因异常监控影响应用性能,错误报告应异步发送
  5. 安全考虑:确保错误报告不包含敏感信息

常见问题解决方案

问题场景解决方案相关代码
渲染进程白屏监控window-ready事件超时,实现自动刷新electron/preload/lifecycle.js
主进程崩溃使用electron-crash-reporter捕获崩溃转储electron/main.js
日志文件过大实现日志轮转,按大小或日期分割electron/config/config.default.js
网络错误上报失败实现本地缓存和重试机制electron/utils/errorMonitor.js

通过本文介绍的方法,开发者可以基于electron-egg框架构建专业的应用异常监控平台,有效提升应用稳定性和用户体验。完整实现了从异常捕获、日志记录到问题定位的全流程解决方案,满足企业级桌面应用的监控需求。

【免费下载链接】electron-egg A simple, cross platform, enterprise desktop software development framework 【免费下载链接】electron-egg 项目地址: https://gitcode.com/dromara/electron-egg

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值