diff --git a/src/main/bootstrap/process-guards.ts b/src/main/bootstrap/process-guards.ts index ff24df3..d97a5f0 100644 --- a/src/main/bootstrap/process-guards.ts +++ b/src/main/bootstrap/process-guards.ts @@ -3,9 +3,9 @@ import logger from '../services/logger/index' import { logAudit, closeAuditLogger } from '../services/logger/audit-logger' export function setupProcessGuards(): void { - process.on('uncaughtException', async (err) => { + process.on('uncaughtException', (err) => { logger.error('Uncaught exception', { error: err }) - await logAudit('SYSTEM_CRASH', 'system', { + logAudit('SYSTEM_CRASH', 'system', { username: 'system', computerName: process.env.COMPUTERNAME || 'unknown', resource: 'main-process', @@ -15,9 +15,9 @@ export function setupProcessGuards(): void { setTimeout(() => process.exit(1), 1000) }) - process.on('unhandledRejection', async (reason) => { + process.on('unhandledRejection', (reason) => { logger.error('Unhandled Rejection', { reason: String(reason) }) - await logAudit('SYSTEM_ERROR', 'system', { + logAudit('SYSTEM_ERROR', 'system', { username: 'system', computerName: process.env.COMPUTERNAME || 'unknown', resource: 'main-process', diff --git a/src/main/ipc/extractor-handler.ts b/src/main/ipc/extractor-handler.ts index 4b885c5..d1b093d 100644 --- a/src/main/ipc/extractor-handler.ts +++ b/src/main/ipc/extractor-handler.ts @@ -292,7 +292,7 @@ export function registerExtractorHandlers(): void { recordCount: result.recordCount, errorCount: result.errors.length } - }).catch((err) => log.warn('Failed to write audit log', { err })) + }) } return result diff --git a/src/main/ipc/logger-handler.ts b/src/main/ipc/logger-handler.ts index 74f3b74..e3c4474 100644 --- a/src/main/ipc/logger-handler.ts +++ b/src/main/ipc/logger-handler.ts @@ -9,7 +9,7 @@ * - Error-level logs bypass circuit breaker */ -import { ipcMain, BrowserWindow } from 'electron' +import { ipcMain } from 'electron' import winston from 'winston' import { createLogger } from '../services/logger' import logger from '../services/logger' diff --git a/src/main/ipc/settings-handler.ts b/src/main/ipc/settings-handler.ts index 9e3377a..747d2bb 100644 --- a/src/main/ipc/settings-handler.ts +++ b/src/main/ipc/settings-handler.ts @@ -74,7 +74,7 @@ export function registerSettingsHandlers(): void { resource: 'ERP_CONFIG', status: 'success', metadata: { changeType: 'erp_credentials', usernameChanged: !!settings.erp.username } - }).catch((err) => log.warn('Failed to write audit log', { err })) + }) } return { success: true } diff --git a/src/main/services/auth/auth-application-service.ts b/src/main/services/auth/auth-application-service.ts index 209701a..46c3d2b 100644 --- a/src/main/services/auth/auth-application-service.ts +++ b/src/main/services/auth/auth-application-service.ts @@ -176,8 +176,6 @@ export class AuthApplicationService { actorId: string, payload: Parameters[2] ): void { - logAudit(action, actorId, payload).catch((err) => - log.warn('Failed to write audit log', { err }) - ) + logAudit(action, actorId, payload) } } diff --git a/src/main/services/cleaner/cleaner-application-service.ts b/src/main/services/cleaner/cleaner-application-service.ts index 0feeea0..96fce84 100644 --- a/src/main/services/cleaner/cleaner-application-service.ts +++ b/src/main/services/cleaner/cleaner-application-service.ts @@ -274,7 +274,7 @@ export class CleanerApplicationService { ? 'failure' : 'success' - await logAudit('CLEAN', String(currentUser.id), { + logAudit('CLEAN', String(currentUser.id), { username: currentUser.username, computerName: (await import('os')).hostname(), resource: 'MATERIAL_PLAN', @@ -288,7 +288,7 @@ export class CleanerApplicationService { materialsSkipped: result.materialsSkipped, errorCount: result.errors.length } - }).catch((err) => log.warn('Failed to write audit log', { err })) + }) } private async generateAndUploadReport( diff --git a/src/main/services/logger/audit-logger.ts b/src/main/services/logger/audit-logger.ts index 4ad39a7..9500af1 100644 --- a/src/main/services/logger/audit-logger.ts +++ b/src/main/services/logger/audit-logger.ts @@ -46,7 +46,7 @@ const jsonlFormat = winston.format.printf(({ message }) => { */ const auditLogger = winston.createLogger({ level: 'info', - silent: false, + silent: true, transports: [] }) @@ -57,6 +57,9 @@ const auditLogger = winston.createLogger({ * @param retentionDays - Number of days to retain audit logs */ export function applyAuditConfig(retentionDays: number): void { + // Enable logging now that config is loaded + auditLogger.silent = false + // Remove existing DailyRotateFile transports const existingTransports = auditLogger.transports.filter((t) => t instanceof DailyRotateFile) for (const transport of existingTransports) { @@ -83,9 +86,8 @@ export function applyAuditConfig(retentionDays: number): void { * @param action - The action that was performed * @param userId - User ID who performed the action * @param details - Additional details including username, computerName, resource, status, and optional metadata - * @returns Promise that resolves when the log is written (non-blocking) */ -export async function logAudit( +export function logAudit( action: string, userId: string, details: { @@ -95,7 +97,7 @@ export async function logAudit( status: 'success' | 'failure' | 'partial' metadata?: Record } -): Promise { +): void { const entry: AuditEntry = { timestamp: new Date().toISOString(), action, @@ -115,8 +117,7 @@ export async function logAudit( /** * Flush and close the audit logger (call on app shutdown) */ -export async function closeAuditLogger(): Promise { - // Winston logger.close() is synchronous +export function closeAuditLogger(): void { auditLogger.close() } diff --git a/src/main/services/logger/index.ts b/src/main/services/logger/index.ts index 49e115d..c6ae1ba 100644 --- a/src/main/services/logger/index.ts +++ b/src/main/services/logger/index.ts @@ -56,7 +56,21 @@ const consoleFormat = winston.format.combine( } } - const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta, null, 2)}` : '' + let metaStr = '' + if (Object.keys(meta).length > 0) { + try { + metaStr = ` ${JSON.stringify(meta, null, 2)}` + } catch { + // Fallback for circular references: stringify primitives, replace objects with placeholder + metaStr = ` ${JSON.stringify( + Object.fromEntries( + Object.entries(meta).map(([k, v]) => [k, typeof v === 'object' ? '[Object]' : v]) + ), + null, + 2 + )}` + } + } return `${timestamp} [${level}]${contextStr} ${message}${errorStr}${metaStr}` }) ) @@ -77,9 +91,7 @@ const fileFormat = winston.format.combine( // Serialize any error in meta fields (skip if already serialized) for (const key of Object.keys(info)) { if (key !== 'error' && info[key] instanceof Error) { - info[key] = IS_PROD - ? sanitizeError(serializeError(info[key])) - : serializeError(info[key]) + info[key] = IS_PROD ? sanitizeError(serializeError(info[key])) : serializeError(info[key]) } } diff --git a/src/main/services/logger/shared.ts b/src/main/services/logger/shared.ts index 989b5b0..e319bb4 100644 --- a/src/main/services/logger/shared.ts +++ b/src/main/services/logger/shared.ts @@ -16,6 +16,8 @@ export function getLogDir(): string { return app.getPath('logs') } // Fallback for development or before app is ready + // Note: synchronous FS calls are acceptable here because this branch only + // executes in dev environments when app is not yet ready (rare, at startup). const devLogDir = path.join(process.cwd(), 'logs') if (!fs.existsSync(devLogDir)) { fs.mkdirSync(devLogDir, { recursive: true }) diff --git a/tests/unit/audit-logger.test.ts b/tests/unit/audit-logger.test.ts index b62f80a..f070d94 100644 --- a/tests/unit/audit-logger.test.ts +++ b/tests/unit/audit-logger.test.ts @@ -204,8 +204,8 @@ describe('Audit Logger - Real File Integration', () => { it('should close audit logger without errors', async () => { const { closeAuditLogger } = await import('../../src/main/services/logger/audit-logger') - // Should resolve without throwing - await expect(closeAuditLogger()).resolves.toBeUndefined() + // Should complete without throwing + expect(() => closeAuditLogger()).not.toThrow() }) it('should handle special characters in fields', async () => {