Skip to content

Kapitel 10: Debugging und Fehlerbehandlung

In diesem Kapitel lernen Sie, wie Sie Electron-Anwendungen effektiv debuggen und Fehler behandeln.

10.1 Renderer-Prozess-Debugging

Entwicklerwerkzeuge (DevTools)

javascript
// main.js
const { BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({ /* ... */ });
  
  // DevTools öffnen (nur Entwicklung)
  win.webContents.openDevTools();
  
  win.loadFile('index.html');
}

Verschiedene Methoden zum Öffnen von DevTools

javascript
// Im Hauptprozess
win.webContents.openDevTools();  // Standard
win.webContents.openDevTools({ mode: 'detach' });  // Abgetrennt
win.webContents.openDevTools({ mode: 'right' });   // Rechts
win.webContents.openDevTools({ mode: 'bottom' });  // Unten

// Im Renderer-Prozess (über IPC)
ipcMain.on('open-devtools', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  win.webContents.openDevTools();
});

Konsolenausgaben im Renderer

javascript
// renderer.js
console.log('Normale Nachricht');
console.info('Information');
console.warn('Warnung');
console.error('Fehler');

// Objekte ausgeben
const user = { name: 'Max', age: 25 };
console.log('Benutzer:', user);

// Tabelle anzeigen
console.table([{ id: 1, name: 'Max' }, { id: 2, name: 'Anna' }]);

// Zeitmessung
console.time('Ladezeit');
// ... Code ausführen
console.timeEnd('Ladezeit');

Breakpoints setzen (VS Code)

json
// .vscode/launch.json (Renderer-Prozess)
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Renderer Debug",
      "type": "chrome",
      "request": "attach",
      "port": 9222,
      "webRoot": "${workspaceFolder}"
    }
  ]
}
javascript
// Mit Debug-Flag starten
// package.json
{
  "scripts": {
    "debug": "electron . --remote-debugging-port=9222"
  }
}

10.2 Hauptprozess-Debugging

VS Code Debugging-Konfiguration

json
// .vscode/launch.json (Hauptprozess)
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Electron: Hauptprozess",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
      },
      "args": ["."],
      "outputCapture": "std",
      "env": {
        "NODE_ENV": "development"
      }
    }
  ]
}

Konsolenausgaben im Hauptprozess

javascript
// main.js
const { app } = require('electron');

// Einfache Ausgabe
console.log('App wird gestartet...');
console.log('App-Version:', app.getVersion());
console.log('App-Pfad:', app.getAppPath());

// Fehler ausgeben
try {
  // Code, der Fehler werfen könnte
  throw new Error('Ein Fehler ist aufgetreten');
} catch (error) {
  console.error('Fehler:', error.message);
  console.error('Stack-Trace:', error.stack);
}

// Mit Präfix (zur Unterscheidung)
console.log('[Main] App bereit');
console.log('[IPC] Nachricht empfangen');

Debugging mit node-inspect

bash
# Mit --inspect Flag starten
npx electron . --inspect=9229

# Oder in package.json
{
  "scripts": {
    "debug-main": "electron . --inspect-brk"
  }
}

Dann in Chrome öffnen: chrome://inspect

10.3 Häufige Fehlertypen und Behebungsmethoden

Fehlertyp 1: Anwendung startet nicht

Symptome und Ursachen

SymptomUrsacheLösung
Cannot find module 'electron'Nicht installiertnpm install electron
main.js not foundFalscher Pfad in package.jsonmain-Feld prüfen
Fenster öffnet sich nichtFehler in main.jsIn Konsole prüfen
app is not definedFalscher Importconst { app } = require('electron')

Diagnose-Schritte

bash
# 1. Abhängigkeiten prüfen
npm list electron

# 2. main.js auf Syntaxfehler prüfen
node -c main.js

# 3. Electron-Version prüfen
npx electron --version

# 4. Mit Debug-Output starten
DEBUG=* npx electron .

Fehlertyp 2: IPC-Kommunikationsfehler

Häufige IPC-Fehler

javascript
// FEHLER 1: Falscher Kanalname
// Main
ipcMain.on('get-data', ...);
// Renderer
ipcRenderer.send('getdata');  // FALSCH! (Vertippter Name)

// LÖSUNG: Konstanten verwenden
// channels.js
export const Channels = {
  GET_DATA: 'get-data',
  SAVE_DATA: 'save-data'
};

// Main und Renderer importieren

// FEHLER 2: Nachricht wird nicht empfangen
// Main sendet, aber Renderer hört nicht
win.webContents.send('data', data);
// Vergessen: ipcRenderer.on('data', ...) im Renderer

// LÖSUNG: Listener vor dem Senden registrieren
// Preload.js
contextBridge.exposeInMainWorld('api', {
  onData: (callback) => {
    ipcRenderer.on('data', (event, data) => callback(data));
  }
});

Fehlertyp 3: Native API-Aufrufe schlagen fehl

Fehlerbeispiele

javascript
// FEHLER: Notification ohne Hauptprozess verwenden
// (funktioniert nur in einigen Fällen im Renderer)
const { Notification } = require('electron');  // FALSCH im Renderer!

// LÖSUNG: Über IPC aufrufen
// main.js
ipcMain.handle('show-notification', (event, { title, body }) => {
  new Notification({ title, body }).show();
});

// FEHLER: fs im Renderer verwenden (nodeIntegration: false)
const fs = require('fs');  // FALSCH!

// LÖSUNG: Im Hauptprozess ausführen
ipcMain.handle('read-file', async (event, path) => {
  const fs = require('fs').promises;
  const content = await fs.readFile(path, 'utf-8');
  return content;
});

Fehlertyp 4: Packaging-Fehler

bash
# Häufige Packaging-Fehler
# 1. Pfade sind nach dem Packaging falsch
# FALSCH:
fs.readFileSync('./config.json');

# RICHTIG:
const path = require('path');
fs.readFileSync(path.join(__dirname, 'config.json'));

# 2. Abhängigkeiten fehlen im gepackten Build
# LÖSUNG: In package.json prüfen
{
  "dependencies": {
    // Muss hier stehen, nicht in devDependencies!
    "electron-store": "^8.1.0"
  }
}

# 3. native Module nicht neu kompiliert
# LÖSUNG:
npm install --platform=win32 --arch=x64 electron

10.4 Globale Fehlererfassung

Hauptprozess-Fehler abfangen

javascript
// main.js
const { app, BrowserWindow, dialog } = require('electron');

// Unhandled Promise Rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
  
  // Optional: Benutzer benachrichtigen
  const win = BrowserWindow.getFocusedWindow();
  if (win) {
    dialog.showErrorBox(
      'Ein Fehler ist aufgetreten',
      `Fehler: ${reason.message || reason}`
    );
  }
});

// Uncaught Exceptions
process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  
  dialog.showErrorBox(
    'Kritischer Fehler',
    `Die Anwendung wird beendet.\n\nFehler: ${error.message}`
  );
  
  // App beenden
  app.quit();
});

// Renderer-Prozess Absturz
app.on('render-process-gone', (event, webContents, details) => {
  console.error('Renderer-Prozess abgestürzt:', details.reason);
  
  const win = BrowserWindow.fromWebContents(webContents);
  if (win) {
    dialog.showMessageBox(win, {
      type: 'error',
      title: 'Fehler',
      message: 'Der Renderer-Prozess ist abgestürzt.',
      detail: `Grund: ${details.reason}`,
      buttons: ['OK', 'Neu starten']
    }).then(({ response }) => {
      if (response === 1) {
        app.relaunch();
        app.quit();
      }
    });
  }
});

// GPU-Prozess Absturz
app.on('gpu-process-crashed', (event, killed) => {
  console.error('GPU-Prozess abgestürzt:', killed);
});

Renderer-Prozess-Fehler abfangen

javascript
// preload.js oder renderer.js
window.addEventListener('error', (event) => {
  console.error('Globaller Fehler:', event.error);
  
  // An Hauptprozess senden
  ipcRenderer.send('renderer-error', {
    message: event.error?.message || 'Unbekannter Fehler',
    stack: event.error?.stack,
    lineno: event.lineno,
    colno: event.colno
  });
});

// Unhandled Promise Rejections im Renderer
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled Promise Rejection:', event.reason);
  
  ipcRenderer.send('renderer-error', {
    message: 'Unhandled Promise Rejection',
    reason: event.reason
  });
});

// Fehler an Hauptprozess senden und dort behandeln
ipcRenderer.on('error-response', (event, { title, message }) => {
  alert(`${title}\n${message}`);
});
javascript
// main.js - Fehler vom Renderer empfangen
ipcMain.on('renderer-error', (event, errorInfo) => {
  console.error('Fehler vom Renderer:', errorInfo);
  
  const win = BrowserWindow.fromWebContents(event.sender);
  if (win) {
    dialog.showErrorBox(
      'Fehler in der Benutzeroberfläche',
      `Fehler: ${errorInfo.message}\n\nStack: ${errorInfo.stack || 'N/A'}`
    );
  }
});

Fehlerprotokollierung (Logging)

javascript
// logger.js (eigener Logger)
const fs = require('fs').promises;
const path = require('path');

class Logger {
  constructor(logPath) {
    this.logPath = logPath;
    this.ensureLogDirectory();
  }

  async ensureLogDirectory() {
    const dir = path.dirname(this.logPath);
    try {
      await fs.mkdir(dir, { recursive: true });
    } catch (error) {
      console.error('Fehler beim Erstellen des Log-Verzeichnisses:', error);
    }
  }

  async log(level, message, meta = {}) {
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      ...meta
    };

    const logLine = JSON.stringify(logEntry) + '\n';

    try {
      await fs.appendFile(this.logPath, logLine);
    } catch (error) {
      console.error('Fehler beim Schreiben der Logdatei:', error);
    }

    // Auch in Konsole ausgeben
    console[level](message, meta);
  }

  info(message, meta) {
    return this.log('info', message, meta);
  }

  warn(message, meta) {
    return this.log('warn', message, meta);
  }

  error(message, meta) {
    return this.log('error', message, meta);
  }

  debug(message, meta) {
    if (process.env.NODE_ENV === 'development') {
      return this.log('debug', message, meta);
    }
  }
}

module.exports = Logger;
javascript
// main.js mit Logger verwenden
const Logger = require('./logger');
const log = new Logger(path.join(app.getPath('userData'), 'app.log'));

app.whenReady().then(() => {
  log.info('App gestartet', { version: app.getVersion() });
  
  try {
    createWindow();
    log.info('Hauptfenster erstellt');
  } catch (error) {
    log.error('Fehler beim Erstellen des Fensters', { error: error.message });
  }
});

// Fehler global abfangen
process.on('uncaughtException', (error) => {
  log.error('Uncaught Exception', { 
    error: error.message, 
    stack: error.stack 
  });
  app.quit();
});

10.5 Praxisbeispiel: Anwendungsfehler debuggen und globale Fehlererfassung

Projekt: Fehlerbehandlungs-System

main.js (Hauptprozess mit Fehlerbehandlung)

javascript
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const Logger = require('./logger');

const log = new Logger(path.join(app.getPath('userData'), 'app.log'));

let mainWin;

// Globale Fehlerbehandlung
process.on('unhandledRejection', (reason, promise) => {
  log.error('Unhandled Rejection', { reason: reason?.message || reason });
});

process.on('uncaughtException', (error) => {
  log.error('Uncaught Exception', { 
    error: error.message, 
    stack: error.stack 
  });
  
  dialog.showErrorBox(
    'Kritischer Fehler',
    `Die Anwendung wird beendet.\n\n${error.message}`
  );
  
  app.quit();
});

function createWindow() {
  try {
    mainWin = new BrowserWindow({
      width: 1000,
      height: 700,
      webPreferences: {
        preload: path.join(__dirname, 'preload.js')
      }
    });

    log.info('Fenster erstellt');

    // DevTools im Entwicklungsmodus
    if (process.env.NODE_ENV === 'development') {
      mainWin.webContents.openDevTools();
    }

    // Renderer-Absturz überwachen
    mainWin.webContents.on('render-process-gone', (event, details) => {
      log.error('Renderer-Prozess abgestürzt', { reason: details.reason });
      
      dialog.showMessageBox(mainWin, {
        type: 'error',
        title: 'Fehler',
        message: 'Die Benutzeroberfläche ist abgestürzt.',
        detail: `Grund: ${details.reason}`,
        buttons: ['OK', 'App neu starten']
      }).then(({ response }) => {
        if (response === 1) {
          app.relaunch();
          app.quit();
        }
      });
    });

    mainWin.loadFile('index.html');
    
    mainWin.on('closed', () => {
      mainWin = null;
      log.info('Fenster geschlossen');
    });

  } catch (error) {
    log.error('Fehler beim Erstellen des Fensters', { error: error.message });
    throw error;
  }
}

// IPC-Fehlerbehandlung
ipcMain.handle('risky-operation', async (event, data) => {
  try {
    log.info('Riskante Operation gestartet', { data });
    
    // Simuliere eine riskante Operation
    if (!data || !data.value) {
      throw new Error('Ungültige Daten');
    }

    const result = await someAsyncOperation(data);
    
    log.info('Riskante Operation erfolgreich', { result });
    return { success: true, result };
  } catch (error) {
    log.error('Fehler bei riskanter Operation', { 
      error: error.message, 
      data 
    });
    return { success: false, error: error.message };
  }
});

// Fehler vom Renderer empfangen
ipcMain.on('renderer-error', (event, errorInfo) => {
  log.error('Fehler vom Renderer', errorInfo);
});

app.whenReady().then(() => {
  log.info('App bereit');
  createWindow();
});

app.on('window-all-closed', () => {
  log.info('Alle Fenster geschlossen');
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

preload.js (IPC-Brücke mit Fehlerbehandlung)

javascript
const { contextBridge, ipcRenderer } = require('electron');

// Globale Fehlerbehandlung im Renderer
window.addEventListener('error', (event) => {
  ipcRenderer.send('renderer-error', {
    message: event.error?.message || 'Unbekannter Fehler',
    stack: event.error?.stack,
    lineno: event.lineno,
    colno: event.colno,
    filename: event.filename
  });
});

window.addEventListener('unhandledrejection', (event) => {
  ipcRenderer.send('renderer-error', {
    message: 'Unhandled Promise Rejection',
    reason: event.reason?.message || event.reason
  });
});

contextBridge.exposeInMainWorld('appAPI', {
  doRiskyOperation: (data) => 
    ipcRenderer.invoke('risky-operation', data),
  
  // Fehler manuell melden
  reportError: (errorInfo) => 
    ipcRenderer.send('renderer-error', errorInfo)
});

index.html (UI mit Fehlerbehandlung)

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Fehlerbehandlung Beispiel</title>
  <style>
    body { font-family: Arial; padding: 20px; }
    button { padding: 10px 20px; margin: 5px; cursor: pointer; }
    #output { 
      margin-top: 20px; 
      padding: 10px; 
      background: #f0f0f0; 
      min-height: 100px;
    }
    .error { color: red; }
    .success { color: green; }
  </style>
</head>
<body>
  <h1>Fehlerbehandlung Beispiel</h1>
  
  <button onclick="doOperation()">Operation ausführen</button>
  <button onclick="doFailingOperation()">Fehlerhafte Operation</button>
  <button onclick="throwError()">Fehler werfen</button>
  <button onclick="throwPromiseError()">Promise-Fehler</button>

  <div id="output"></div>

  <script>
    const output = document.getElementById('output');

    function log(message, type = 'info') {
      const div = document.createElement('div');
      div.className = type;
      div.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
      output.appendChild(div);
      output.scrollTop = output.scrollHeight;
    }

    async function doOperation() {
      try {
        log('Operation gestartet...');
        const result = await window.appAPI.doRiskyOperation({ value: 42 });
        
        if (result.success) {
          log('Operation erfolgreich: ' + JSON.stringify(result.result), 'success');
        } else {
          log('Operation fehlgeschlagen: ' + result.error, 'error');
        }
      } catch (error) {
        log('Fehler: ' + error.message, 'error');
      }
    }

    async function doFailingOperation() {
      try {
        log('Fehlerhafte Operation gestartet...');
        const result = await window.appAPI.doRiskyOperation(null);  // Löst Fehler aus
        
        if (!result.success) {
          log('Erwarteter Fehler: ' + result.error, 'error');
        }
      } catch (error) {
        log('Fehler: ' + error.message, 'error');
      }
    }

    function throwError() {
      try {
        log('Werfe Fehler...');
        throw new Error('Dies ist ein manuell geworfener Fehler!');
      } catch (error) {
        log('Fehler abgefangen: ' + error.message, 'error');
        window.appAPI.reportError({
          message: error.message,
          stack: error.stack
        });
      }
    }

    function throwPromiseError() {
      log('Werfe Promise-Fehler...');
      Promise.reject(new Error('Unhandled Promise Rejection!'))
        .catch(() => {});  // Absichtlich nicht behandelt (wird global abgefangen)
    }

    // Globaler Error Handler
    window.onerror = (message, source, lineno, colno, error) => {
      log(`Globaler Fehler: ${message} (Zeile ${lineno})`, 'error');
    };
  </script>
</body>
</html>

Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • Renderer-Prozess mit DevTools zu debuggen
  • Hauptprozess mit VS Code zu debuggen
  • Häufige Fehlertypen zu diagnostizieren und zu beheben
  • Globale Fehlererfassung zu implementieren
  • Ein praktisches Fehlerbehandlungs-System zu erstellen

Im nächsten Kapitel werden wir mit praktischen Übungen beginnen (Teil 4: Umfassende Praxis).

Frei für alle Anfänger