diff --git a/apps/stage-tamagotchi/src/main/index.ts b/apps/stage-tamagotchi/src/main/index.ts index 3f1a19ce0..b5cd31988 100644 --- a/apps/stage-tamagotchi/src/main/index.ts +++ b/apps/stage-tamagotchi/src/main/index.ts @@ -83,6 +83,7 @@ electronApp.setAppUserModelId('ai.moeru.airi') initScreenCaptureForMain() let fileLogger: FileLoggerHandle = nullFileLoggerHandle +let skipFileLogging = false app.whenReady().then(async () => { // Initialize file logger and register the hook @@ -90,9 +91,9 @@ app.whenReady().then(async () => { // Register the global hook for file logging setGlobalHookPostLog((_, formatted) => { - if (fileLogger.logFileFd !== null) { - void fileLogger.appendLog(formatted) - } + if (skipFileLogging || fileLogger.logFileFd === null) + return + void fileLogger.appendLog(formatted) }) injeca.setLogger(createLoggLogger(useLogg('injeca').useGlobalConfig())) @@ -211,13 +212,37 @@ async function handleAppExit() { appExiting = true + let exitedNormally = true + + /** + * Safely execute fn and log any errors that occur, marking the exit as abnormal + * if an error is caught. + * + * @param operation - A verb phrase describing the operation. + * @param fn - Any function to execute. It can be either sync or async. + * @returns A promise that resolves when the operation is complete. + */ + async function logIfError(operation: string, fn: () => unknown): Promise { + try { + await fn() + } + catch (error) { + exitedNormally = false + log.withError(error).error(`[app-exit] Failed to ${operation}:`) + } + } + await Promise.all([ - emitAppBeforeQuit(), - injeca.stop(), - fileLogger.close(), // Ensure all logs are flushed + logIfError('execute onAppBeforeQuit hooks', () => emitAppBeforeQuit()), + logIfError('stop injeca', () => injeca.stop()), ]) - app.exit(0) + // Prevent the global log hook from trying to write to the file after close() is called, + // which would cause a recursive failure if close() itself throws. + skipFileLogging = true + await logIfError('flush file logs', () => fileLogger.close()) // Ensure all logs are flushed + + app.exit(exitedNormally ? 0 : 1) } process.on('SIGINT', () => handleAppExit())