161 lines
4.6 KiB
TypeScript
161 lines
4.6 KiB
TypeScript
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<string, unknown> {
|
|
return {
|
|
webContentsId: webContents.id,
|
|
type: webContents.getType(),
|
|
url: webContents.getURL(),
|
|
loading: webContents.isLoading(),
|
|
destroyed: webContents.isDestroyed(),
|
|
};
|
|
}
|
|
|
|
function describeWindow(window: BrowserWindow): Record<string, unknown> {
|
|
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<string, unknown> = {}): 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(),
|
|
});
|
|
}
|