Appearance
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
| Symptom | Ursache | Lösung |
|---|---|---|
Cannot find module 'electron' | Nicht installiert | npm install electron |
main.js not found | Falscher Pfad in package.json | main-Feld prüfen |
| Fenster öffnet sich nicht | Fehler in main.js | In Konsole prüfen |
app is not defined | Falscher Import | const { 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 electron10.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).
