feat(diagnostics): full crash instrumentation — never silently die again
This commit is contained in:
parent
d159ac484a
commit
9b10a4356f
@ -67,6 +67,10 @@ class UploadManager extends EventEmitter {
|
||||
return this._accountOverrides.get(hoster) || null;
|
||||
}
|
||||
|
||||
getActiveJobCount() {
|
||||
return this.activeJobs.size;
|
||||
}
|
||||
|
||||
clearFailedAccount(hoster, accountId) {
|
||||
return this._failedAccounts.delete(`${hoster}:${accountId}`);
|
||||
}
|
||||
|
||||
90
main.js
90
main.js
@ -227,11 +227,51 @@ function rotLog(msg, ts) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Catch unhandled rejections from fire-and-forget async calls
|
||||
function _writeCrashLog(prefix, err, extra) {
|
||||
try {
|
||||
const ts = new Date().toISOString();
|
||||
const line = `[${ts}] ${prefix} ${err && err.stack ? err.stack : (err && err.message) || String(err)}${extra ? ' :: ' + JSON.stringify(extra) : ''}\n`;
|
||||
try {
|
||||
const target = getDebugLogPath();
|
||||
fs.appendFileSync(target, line, 'utf-8');
|
||||
} catch {}
|
||||
try {
|
||||
const crashDir = path.dirname(getDebugLogPath());
|
||||
fs.appendFileSync(path.join(crashDir, 'crash.log'), line, 'utf-8');
|
||||
} catch {}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
debugLog(`UNHANDLED REJECTION: ${reason && reason.stack ? reason.stack : reason}`);
|
||||
_writeCrashLog('UNHANDLED REJECTION', reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err, origin) => {
|
||||
_writeCrashLog('UNCAUGHT EXCEPTION (' + origin + ')', err);
|
||||
debugLog(`UNCAUGHT EXCEPTION (${origin}): ${err && err.stack ? err.stack : err}`);
|
||||
});
|
||||
|
||||
process.on('exit', (code) => {
|
||||
try { _writeCrashLog('PROCESS EXIT', new Error('code=' + code)); } catch {}
|
||||
});
|
||||
|
||||
process.on('warning', (warning) => {
|
||||
debugLog(`PROCESS WARNING: ${warning.name} ${warning.message}`);
|
||||
});
|
||||
|
||||
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK']) {
|
||||
try {
|
||||
process.on(sig, () => {
|
||||
_writeCrashLog('SIGNAL ' + sig, new Error('process received ' + sig));
|
||||
try {
|
||||
if (_debugLogBuffer.length) fs.appendFileSync(getDebugLogPath(), _debugLogBuffer.join(''), 'utf-8');
|
||||
} catch {}
|
||||
process.exit(0);
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function withTimeout(promise, timeoutMs, label) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
@ -947,6 +987,51 @@ function createWindow() {
|
||||
});
|
||||
|
||||
mainWindow.webContents.setBackgroundThrottling(false);
|
||||
|
||||
mainWindow.webContents.on('render-process-gone', (_event, details) => {
|
||||
_writeCrashLog('RENDER PROCESS GONE', new Error(details.reason || 'unknown'), details);
|
||||
debugLog(`RENDER PROCESS GONE: reason=${details.reason} exitCode=${details.exitCode}`);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
try {
|
||||
const choice = dialog.showMessageBoxSync(mainWindow, {
|
||||
type: 'error',
|
||||
title: 'Renderer abgestürzt',
|
||||
message: `Der Renderer-Prozess ist abgestürzt (${details.reason}).`,
|
||||
detail: 'Bitte Diagnose-Paket exportieren und einsenden. Klick "Neu laden" um die UI wiederherzustellen — laufende Uploads im Main-Process bleiben aktiv.',
|
||||
buttons: ['Neu laden', 'Beenden'],
|
||||
defaultId: 0,
|
||||
cancelId: 1
|
||||
});
|
||||
if (choice === 0) {
|
||||
mainWindow.webContents.reload();
|
||||
} else {
|
||||
app.exit(1);
|
||||
}
|
||||
} catch {
|
||||
try { mainWindow.webContents.reload(); } catch {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('unresponsive', () => {
|
||||
_writeCrashLog('RENDERER UNRESPONSIVE', new Error('webContents unresponsive'));
|
||||
debugLog('RENDERER UNRESPONSIVE');
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('responsive', () => {
|
||||
debugLog('RENDERER RESPONSIVE AGAIN');
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => {
|
||||
_writeCrashLog('DID-FAIL-LOAD', new Error(errorDescription), { errorCode, validatedURL });
|
||||
debugLog(`DID-FAIL-LOAD: ${errorCode} ${errorDescription} url=${validatedURL}`);
|
||||
});
|
||||
|
||||
app.on('child-process-gone', (_event, details) => {
|
||||
_writeCrashLog('CHILD PROCESS GONE', new Error(details.reason || 'unknown'), details);
|
||||
debugLog(`CHILD PROCESS GONE: type=${details.type} reason=${details.reason} exitCode=${details.exitCode}`);
|
||||
});
|
||||
|
||||
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||
}
|
||||
|
||||
@ -1049,6 +1134,9 @@ app.whenReady().then(() => {
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
const activeJobs = uploadManager && typeof uploadManager.getActiveJobCount === 'function' ? uploadManager.getActiveJobCount() : 0;
|
||||
debugLog(`window-all-closed: activeJobs=${activeJobs}, uploadManager=${!!uploadManager}`);
|
||||
_writeCrashLog('WINDOW-ALL-CLOSED', new Error('all windows closed'), { activeJobs, uploadManager: !!uploadManager });
|
||||
app.quit();
|
||||
});
|
||||
|
||||
|
||||
@ -77,6 +77,19 @@ let _sessionErrorCount = 0;
|
||||
// Huge with thousands of rows × thousands of incoming results.
|
||||
const _sessionFileKeys = new Set();
|
||||
|
||||
window.addEventListener('error', (e) => {
|
||||
try {
|
||||
const msg = `RENDERER ERROR: ${e.message} at ${e.filename}:${e.lineno}:${e.colno}${e.error && e.error.stack ? '\n' + e.error.stack : ''}`;
|
||||
if (window.api && window.api.debugLog) window.api.debugLog(msg);
|
||||
} catch {}
|
||||
});
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
try {
|
||||
const reason = e.reason && e.reason.stack ? e.reason.stack : (e.reason && e.reason.message) || String(e.reason);
|
||||
if (window.api && window.api.debugLog) window.api.debugLog(`RENDERER UNHANDLED REJECTION: ${reason}`);
|
||||
} catch {}
|
||||
});
|
||||
|
||||
// --- Init ---
|
||||
async function init() {
|
||||
config = await window.api.getConfig();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user