feat: enhance logging capabilities, implement runtime diagnostics, and update preinstalled skills metadata

This commit is contained in:
DEV_DSW
2026-04-27 17:00:51 +08:00
parent 90e02636c7
commit 2081f583e3
9 changed files with 354 additions and 24 deletions

View File

@@ -0,0 +1,66 @@
export type SerializedError = {
name?: string;
message: string;
stack?: string;
code?: string | number;
cause?: unknown;
};
function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
export function serializeError(error: Error): SerializedError {
const code = (error as Error & { code?: string | number }).code;
const cause = (error as Error & { cause?: unknown }).cause;
return {
name: error.name,
message: error.message,
stack: error.stack,
...(typeof code !== 'undefined' ? { code } : {}),
...(typeof cause !== 'undefined' ? { cause: serializeUnknownForLog(cause) } : {}),
};
}
export function serializeUnknownError(error: unknown): SerializedError {
if (error instanceof Error) {
return serializeError(error);
}
if (typeof error === 'string') {
return { message: error };
}
if (isPlainObject(error) && typeof error.message === 'string') {
return {
message: error.message,
...(typeof error.name === 'string' ? { name: error.name } : {}),
...(typeof error.stack === 'string' ? { stack: error.stack } : {}),
...(typeof error.code === 'string' || typeof error.code === 'number' ? { code: error.code } : {}),
...(typeof error.cause !== 'undefined' ? { cause: serializeUnknownForLog(error.cause) } : {}),
};
}
return {
message: `Non-Error value thrown: ${String(error)}`,
};
}
export function serializeUnknownForLog(value: unknown): unknown {
if (value instanceof Error) {
return serializeError(value);
}
if (Array.isArray(value)) {
return value.map((item) => serializeUnknownForLog(item));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, serializeUnknownForLog(entryValue)]),
);
}
return value;
}

View File

@@ -0,0 +1,160 @@
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(),
});
}