import { app, crashReporter, type BrowserWindow, type WebContents } from 'electron'; import path from 'node:path'; import logManager from '@electron/service/logger'; import { ensureDir, getUserDataDir } from './paths'; import { serializeUnknownError, serializeUnknownForLog } from './log-helpers'; let diagnosticsInstalled = false; let crashReporterStarted = false; function describeWebContents(webContents: WebContents): Record { return { webContentsId: webContents.id, type: webContents.getType(), url: webContents.getURL(), loading: webContents.isLoading(), destroyed: webContents.isDestroyed(), }; } function describeWindow(window: BrowserWindow): Record { return { windowId: window.id, title: window.getTitle(), visible: window.isVisible(), minimized: window.isMinimized(), maximized: window.isMaximized(), destroyed: window.isDestroyed(), ...describeWebContents(window.webContents), }; } function getCrashDumpsDirectoryPath(): string { return ensureDir(path.join(getUserDataDir(), 'logs', 'crashDumps')); } function attachWindowDiagnostics(window: BrowserWindow): void { logManager.info('BrowserWindow created', describeWindow(window)); window.on('unresponsive', () => { logManager.warn('BrowserWindow became unresponsive', describeWindow(window)); }); window.on('responsive', () => { logManager.info('BrowserWindow became responsive again', describeWindow(window)); }); window.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => { logManager.error('WebContents failed to load', { ...describeWindow(window), errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId, }); }); window.webContents.on('render-process-gone', (_event, details) => { logManager.error('Window render process exited unexpectedly', { ...describeWindow(window), reason: details.reason, exitCode: details.exitCode, }); }); } export function logStartupCheckpoint(stage: string, details: Record = {}): void { logManager.info(`startup:${stage}`, { pid: process.pid, packaged: app.isPackaged, ...details, }); } export function startLocalCrashReporter(): void { if (crashReporterStarted) { return; } const crashDumpsDir = getCrashDumpsDirectoryPath(); try { app.setPath('crashDumps', crashDumpsDir); } catch (error) { logManager.captureException('Failed to set crashDumps path', error, { crashDumpsDir }); } try { crashReporter.start({ companyName: 'ZhiNian Team', productName: app.getName() || 'NIANXX', submitURL: 'https://127.0.0.1/disabled-crash-upload', uploadToServer: false, compress: true, ignoreSystemCrashHandler: false, rateLimit: false, globalExtra: { appVersion: app.getVersion(), packaged: String(app.isPackaged), platform: process.platform, arch: process.arch, }, }); crashReporterStarted = true; logManager.info('Crash reporter started for local dump collection', { crashDumpsDir, uploadToServer: false, }); } catch (error) { logManager.captureException('Failed to start crash reporter', error, { crashDumpsDir }); } } export function installRuntimeDiagnostics(): void { if (diagnosticsInstalled) { return; } diagnosticsInstalled = true; process.on('warning', (warning) => { logManager.warn('Node.js process warning', serializeUnknownError(warning)); }); process.on('uncaughtExceptionMonitor', (error) => { logManager.captureException('uncaughtExceptionMonitor', error); }); process.on('unhandledRejection', (reason) => { logManager.error('unhandledRejection observed by runtime diagnostics', { reason: serializeUnknownForLog(reason), }); }); app.on('browser-window-created', (_event, window) => { attachWindowDiagnostics(window); }); app.on('render-process-gone', (_event, webContents, details) => { logManager.error('App render process exited unexpectedly', { ...describeWebContents(webContents), reason: details.reason, exitCode: details.exitCode, }); }); app.on('child-process-gone', (_event, details) => { logManager.error('Child process exited unexpectedly', serializeUnknownForLog(details)); }); logManager.info('Runtime diagnostics installed', { platform: process.platform, arch: process.arch, pid: process.pid, userDataDir: getUserDataDir(), logsDir: logManager.getLogDirectoryPath(), }); }