乐闻世界logo
搜索文章和话题

Electron Debugging Techniques and Tools

2月18日 10:37

Debugging is an important part of Electron development. Mastering effective debugging techniques and tools can greatly improve development efficiency. This article will detail debugging methods and tools for Electron.

Developer Tools

1. Enable Developer Tools

javascript
// main.js const { app, BrowserWindow } = require('electron') let mainWindow app.whenReady().then(() => { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { devTools: true // Ensure developer tools are enabled } }) mainWindow.loadFile('index.html') // Auto-open developer tools if (process.env.NODE_ENV === 'development') { mainWindow.webContents.openDevTools() } })

2. Keyboard Shortcuts

Electron provides default keyboard shortcuts to open developer tools:

  • Windows/Linux: Ctrl+Shift+I
  • macOS: Cmd+Option+I

3. Custom Keyboard Shortcuts

javascript
// main.js const { app, BrowserWindow, globalShortcut } = require('electron') app.whenReady().then(() => { // Register custom keyboard shortcut globalShortcut.register('CommandOrControl+Shift+D', () => { const focusedWindow = BrowserWindow.getFocusedWindow() if (focusedWindow) { if (focusedWindow.webContents.isDevToolsOpened()) { focusedWindow.webContents.closeDevTools() } else { focusedWindow.webContents.openDevTools() } } }) }) app.on('will-quit', () => { // Unregister all shortcuts globalShortcut.unregisterAll() })

Main Process Debugging

1. Using VS Code Debugging

Create .vscode/launch.json file:

json
{ "version": "0.2.0", "configurations": [ { "name": "Debug Main Process", "type": "node", "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", "windows": { "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" }, "args": ["."], "outputCapture": "std" } ] }

2. Using Chrome DevTools

javascript
// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') // Use console.log in main process console.log('Main process started') console.log('Electron version:', process.versions.electron) console.log('Node.js version:', process.versions.node) console.log('Chrome version:', process.versions.chrome) })

3. Using debugger Statement

javascript
// main.js const { app, BrowserWindow } = require('electron') function initializeApp() { debugger // Set breakpoint here const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') } app.whenReady().then(initializeApp)

Renderer Process Debugging

1. Using Chrome DevTools

Renderer process can directly use Chrome DevTools for debugging:

javascript
// renderer.js console.log('Renderer process started') // Use debugger statement function handleClick() { debugger // Set breakpoint here console.log('Button clicked') } document.getElementById('myButton').addEventListener('click', handleClick)

2. React DevTools

bash
npm install --save-dev react-devtools
javascript
// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { // Install React DevTools const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer') try { installExtension(REACT_DEVELOPER_TOOLS) console.log('React DevTools installed') } catch (error) { console.error('Failed to install React DevTools:', error) } const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') })

3. Vue DevTools

bash
npm install --save-dev vue-devtools
javascript
// main.js const { app, BrowserWindow } = require('electron') app.whenReady().then(() => { // Install Vue DevTools const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer') try { installExtension(VUEJS_DEVTOOLS) console.log('Vue DevTools installed') } catch (error) { console.error('Failed to install Vue DevTools:', error) } const mainWindow = new BrowserWindow({ width: 1200, height: 800 }) mainWindow.loadFile('index.html') })

Performance Analysis

1. Using Performance Panel

javascript
// renderer.js // Start performance recording performance.mark('start') // Perform some operations function performOperation() { // Complex calculations or rendering operations } // End performance recording performance.mark('end') // Measure performance performance.measure('operation', 'start', 'end') const measure = performance.getEntriesByName('operation')[0] console.log(`Operation took ${measure.duration}ms`)

2. Using Chrome Performance API

javascript
// renderer.js // Use Performance Observer const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(`${entry.name}: ${entry.duration}ms`) } }) observer.observe({ entryTypes: ['measure', 'navigation', 'resource'] }) // Measure specific operation function measureFunction(fn) { const start = performance.now() fn() const end = performance.now() console.log(`Function took ${end - start}ms`) }

3. Memory Analysis

javascript
// main.js // Monitor memory usage setInterval(() => { const memoryUsage = process.memoryUsage() console.log('Memory Usage:', { rss: `${Math.round(memoryUsage.rss / 1024 / 1024)} MB`, heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)} MB`, heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)} MB`, external: `${Math.round(memoryUsage.external / 1024 / 1024)} MB` }) }, 30000)
javascript
// renderer.js // Use Chrome Memory Profiler function takeHeapSnapshot() { if (window.performance && window.performance.memory) { console.log('Memory Info:', { usedJSHeapSize: `${Math.round(window.performance.memory.usedJSHeapSize / 1024 / 1024)} MB`, totalJSHeapSize: `${Math.round(window.performance.memory.totalJSHeapSize / 1024 / 1024)} MB`, jsHeapSizeLimit: `${Math.round(window.performance.memory.jsHeapSizeLimit / 1024 / 1024)} MB` }) } } // Periodically check memory setInterval(takeHeapSnapshot, 10000)

Network Debugging

1. Using Network Panel

javascript
// main.js const { session } = require('electron') app.whenReady().then(() => { // Listen to network requests session.defaultSession.webRequest.onBeforeRequest((details, callback) => { console.log('Request:', details.url) callback({}) }) session.defaultSession.webRequest.onCompleted((details) => { console.log('Response:', details.url, details.statusCode) }) session.defaultSession.webRequest.onErrorOccurred((details) => { console.error('Error:', details.url, details.error) }) })

2. Intercept and Modify Requests

javascript
// main.js const { session } = require('electron') app.whenReady().then(() => { // Intercept requests session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { // Add custom request headers details.requestHeaders['X-Custom-Header'] = 'CustomValue' callback({ requestHeaders: details.requestHeaders }) }) // Modify responses session.defaultSession.webRequest.onHeadersReceived((details, callback) => { // Add custom response headers details.responseHeaders['X-Custom-Response'] = ['CustomResponse'] callback({ responseHeaders: details.responseHeaders }) }) })

Logging

1. Using electron-log

bash
npm install electron-log
javascript
// main.js const log = require('electron-log') // Configure logging log.transports.file.level = 'debug' log.transports.console.level = 'debug' // Log messages log.info('Application started') log.debug('Debug information') log.warn('Warning message') log.error('Error occurred') // Log objects log.info('User data:', { name: 'John', age: 30 }) // Log error stack try { // Code that might fail } catch (error) { log.error('Error:', error) }

2. Custom Logging System

javascript
// logger.js const fs = require('fs').promises const path = require('path') const { app } = require('electron') class Logger { constructor() { this.logPath = path.join(app.getPath('userData'), 'logs') this.currentLogFile = path.join(this.logPath, this.getLogFileName()) } getLogFileName() { const date = new Date() return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}.log` } async log(level, message, data = null) { const timestamp = new Date().toISOString() const logEntry = `[${timestamp}] [${level}] ${message}` if (data) { console.log(logEntry, data) await this.writeToFile(logEntry + ' ' + JSON.stringify(data)) } else { console.log(logEntry) await this.writeToFile(logEntry) } } async writeToFile(content) { try { await fs.mkdir(this.logPath, { recursive: true }) await fs.appendFile(this.currentLogFile, content + '\n') } catch (error) { console.error('Failed to write log:', error) } } info(message, data) { this.log('INFO', message, data) } debug(message, data) { this.log('DEBUG', message, data) } warn(message, data) { this.log('WARN', message, data) } error(message, data) { this.log('ERROR', message, data) } } module.exports = new Logger()

Error Handling

1. Global Error Handling

javascript
// main.js const { app, dialog } = require('electron') // Catch unhandled exceptions process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error) dialog.showErrorBox('Error', `An error occurred: ${error.message}`) // Log error log.error('Uncaught Exception:', error) }) // Catch unhandled Promise rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason) dialog.showErrorBox('Error', `An error occurred: ${reason}`) // Log error log.error('Unhandled Rejection:', reason) }) // Catch renderer process errors app.on('render-process-gone', (event, webContents, details) => { console.error('Render process gone:', details) dialog.showErrorBox('Error', 'The application encountered an error and needs to restart.') // Reload page if (webContents) { webContents.reload() } })

2. Renderer Process Error Handling

javascript
// renderer.js // Catch global errors window.addEventListener('error', (event) => { console.error('Global error:', event.error) // Send error to main process const { ipcRenderer } = require('electron') ipcRenderer.send('renderer-error', { message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack }) }) // Catch unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason) // Send error to main process const { ipcRenderer } = require('electron') ipcRenderer.send('renderer-error', { reason: event.reason }) })
javascript
// main.js const { ipcMain } = require('electron') ipcMain.on('renderer-error', (event, errorData) => { console.error('Renderer error:', errorData) // Log error log.error('Renderer error:', errorData) // Show error dialog const { dialog } = require('electron') dialog.showErrorBox('Error', 'An error occurred in the renderer process') })

Testing Tools

1. Using Spectron

bash
npm install --save-dev spectron
javascript
// test/app.test.js const Application = require('spectron').Application const path = require('path') describe('Application launch', function() { this.timeout(10000) beforeEach(async function() { this.app = new Application({ path: path.join(__dirname, '..', 'node_modules', '.bin', 'electron'), args: [path.join(__dirname, '..')] }) await this.app.start() }) afterEach(async function() { if (this.app && this.app.isRunning()) { await this.app.stop() } }) it('shows an initial window', async function() { const windowCount = await this.app.client.getWindowCount() assert.equal(windowCount, 1) }) it('window title is correct', async function() { const title = await this.app.client.getTitle() assert.equal(title, 'My Electron App') }) })

2. Using Playwright

bash
npm install --save-dev @playwright/test
javascript
// test/app.spec.js const { test, expect } = require('@playwright/test') const { _electron: electron } = require('playwright') test('launch app', async () => { const app = await electron.launch({ path: require('electron') }) const window = await app.firstWindow() const title = await window.title() expect(title).toBe('My Electron App') await app.close() })

Best Practices

1. Development Environment Configuration

javascript
// config.js const isDevelopment = process.env.NODE_ENV === 'development' module.exports = { isDevelopment, devTools: isDevelopment, logLevel: isDevelopment ? 'debug' : 'info' }

2. Conditional Compilation

javascript
// main.js const { isDevelopment } = require('./config') app.whenReady().then(() => { const mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { devTools: isDevelopment } }) mainWindow.loadFile('index.html') if (isDevelopment) { mainWindow.webContents.openDevTools() } })

3. Error Reporting

javascript
// main.js const Sentry = require('@sentry/electron') Sentry.init({ dsn: 'your-sentry-dsn', environment: process.env.NODE_ENV }) // Automatically capture errors Sentry.captureException(error)

Common Questions

Q: How to use console.log in main process?A: console.log in main process outputs to terminal, you can view logs in terminal.

Q: How to debug multiple windows?A: Each window has independent DevTools, you can open DevTools for each window separately.

Q: How to disable DevTools in production?A: Set webPreferences.devTools to false and remove code that opens DevTools.

Q: How to remotely debug Electron app?A: Start app with --remote-debugging-port parameter, then connect to that port using Chrome.

标签:Electron