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;
|
return this._accountOverrides.get(hoster) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActiveJobCount() {
|
||||||
|
return this.activeJobs.size;
|
||||||
|
}
|
||||||
|
|
||||||
clearFailedAccount(hoster, accountId) {
|
clearFailedAccount(hoster, accountId) {
|
||||||
return this._failedAccounts.delete(`${hoster}:${accountId}`);
|
return this._failedAccounts.delete(`${hoster}:${accountId}`);
|
||||||
}
|
}
|
||||||
|
|||||||
90
main.js
90
main.js
@ -227,11 +227,51 @@ function rotLog(msg, ts) {
|
|||||||
} catch {}
|
} 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) => {
|
process.on('unhandledRejection', (reason) => {
|
||||||
debugLog(`UNHANDLED REJECTION: ${reason && reason.stack ? reason.stack : 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) {
|
function withTimeout(promise, timeoutMs, label) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@ -947,6 +987,51 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.webContents.setBackgroundThrottling(false);
|
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'));
|
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1049,6 +1134,9 @@ app.whenReady().then(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
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();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,19 @@ let _sessionErrorCount = 0;
|
|||||||
// Huge with thousands of rows × thousands of incoming results.
|
// Huge with thousands of rows × thousands of incoming results.
|
||||||
const _sessionFileKeys = new Set();
|
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 ---
|
// --- Init ---
|
||||||
async function init() {
|
async function init() {
|
||||||
config = await window.api.getConfig();
|
config = await window.api.getConfig();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user