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.
This commit is contained in:
Gemini Nexus Bot committed 2026-04-29 03:39:28 -03:00
1 parent 195af14707
commit 9bf7c46ff6
1 file changed
+87 -60
+87 -60
View File
@@ -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;
},
});
/**