From 9bf7c46ff6320f49cba80923c9e11eac35a7d4f0 Mon Sep 17 00:00:00 2001 From: Gemini Nexus Bot Date: Wed, 29 Apr 2026 03:39:28 -0300 Subject: [PATCH] fix: lazy-initialize api-logger to prevent EACCES in CI environments The DailyRotateFile transport was creating directories at module load time, causing EACCES permission errors when tests import modules that transitively reach api-logger.ts. Refactored to use a Proxy that lazily initializes the winston logger on first method call. This eliminates side effects during module loading while maintaining the same public API. --- src/server/logging/api-logger.ts | 147 ++++++++++++++++++------------- 1 file changed, 87 insertions(+), 60 deletions(-) diff --git a/src/server/logging/api-logger.ts b/src/server/logging/api-logger.ts index 368d65d..650dda1 100644 --- a/src/server/logging/api-logger.ts +++ b/src/server/logging/api-logger.ts @@ -8,70 +8,97 @@ import { sanitizeObject } from '../../utils/sensitiveDataMasking'; const LOG_RETENTION = '30d'; const LOG_MAX_SIZE = '50m'; -const agentDir = getAgentDir(); +let _logger: winston.Logger | null = null; -if (!fs.existsSync(agentDir)) { - try { - fs.mkdirSync(agentDir, { recursive: true }); - } catch (e) { - console.error('Failed to create agent directory for API logs', e); +/** + * Lazily initializes the API logger on first access. + * Avoids creating directories and file transports at module load time, + * preventing EACCES errors in CI/test environments. + */ +function getLogger(): winston.Logger { + if (_logger) { + return _logger; } + + const agentDir = getAgentDir(); + + if (!fs.existsSync(agentDir)) { + try { + fs.mkdirSync(agentDir, { recursive: true }); + } catch (e) { + console.error('Failed to create agent directory for API logs', e); + } + } + + const fileFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + const logEntry = { + timestamp, + level, + message, + ...meta, + }; + return JSON.stringify(logEntry); + }), + ); + + const consoleFormat = winston.format.combine( + winston.format.colorize({ all: true }), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + const logEntry = { + timestamp, + level, + message, + ...meta, + }; + return JSON.stringify(logEntry); + }), + ); + + const rotateTransport = new DailyRotateFile({ + filename: path.join(agentDir, 'api-%DATE%.log'), + datePattern: 'YYYY-MM-DD-HH', + maxSize: LOG_MAX_SIZE, + maxFiles: LOG_RETENTION, + zippedArchive: false, + auditFile: path.join(agentDir, '.api-log-audit.json'), + level: 'debug', + format: fileFormat, + }); + + rotateTransport.on('error', (error) => { + console.error('DailyRotateFile API transport error', error); + }); + + const consoleTransport = new winston.transports.Console({ + level: 'debug', + format: consoleFormat, + }); + + consoleTransport.on('error', (error) => { + console.error('Console API transport error', error); + }); + + _logger = winston.createLogger({ + level: 'debug', + transports: [consoleTransport, rotateTransport], + exitOnError: false, + }); + + return _logger; } -const fileFormat = winston.format.combine( - winston.format.timestamp(), - winston.format.printf(({ timestamp, level, message, ...meta }) => { - const logEntry = { - timestamp, - level, - message, - ...meta, - }; - return JSON.stringify(logEntry); - }), -); - -const consoleFormat = winston.format.combine( - winston.format.colorize({ all: true }), - winston.format.printf(({ timestamp, level, message, ...meta }) => { - const logEntry = { - timestamp, - level, - message, - ...meta, - }; - return JSON.stringify(logEntry); - }), -); - -const rotateTransport = new DailyRotateFile({ - filename: path.join(agentDir, 'api-%DATE%.log'), - datePattern: 'YYYY-MM-DD-HH', - maxSize: LOG_MAX_SIZE, - maxFiles: LOG_RETENTION, - zippedArchive: false, - auditFile: path.join(agentDir, '.api-log-audit.json'), - level: 'debug', - format: fileFormat, -}); - -rotateTransport.on('error', (error) => { - console.error('DailyRotateFile API transport error', error); -}); - -const consoleTransport = new winston.transports.Console({ - level: 'debug', - format: consoleFormat, -}); - -consoleTransport.on('error', (error) => { - console.error('Console API transport error', error); -}); - -export const apiLogger = winston.createLogger({ - level: 'debug', - transports: [consoleTransport, rotateTransport], - exitOnError: false, +/** Lazily-initialized API logger instance. */ +export const apiLogger = new Proxy({} as winston.Logger, { + get(_target, prop: string) { + const logger = getLogger(); + const value = logger[prop as keyof winston.Logger]; + if (typeof value === 'function') { + return value.bind(logger); + } + return value; + }, }); /**