Files
zn-ai/electron/utils/runtime-diagnostics.ts

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(),
});
}