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:
1 parent
195af14707
commit
9bf7c46ff6
1 file changed
+87
-60
@@ -8,70 +8,97 @@ import { sanitizeObject } from '../../utils/sensitiveDataMasking';
|
|||||||
const LOG_RETENTION = '30d';
|
const LOG_RETENTION = '30d';
|
||||||
const LOG_MAX_SIZE = '50m';
|
const LOG_MAX_SIZE = '50m';
|
||||||
|
|
||||||
const agentDir = getAgentDir();
|
let _logger: winston.Logger | null = null;
|
||||||
|
|
||||||
if (!fs.existsSync(agentDir)) {
|
/**
|
||||||
try {
|
* Lazily initializes the API logger on first access.
|
||||||
fs.mkdirSync(agentDir, { recursive: true });
|
* Avoids creating directories and file transports at module load time,
|
||||||
} catch (e) {
|
* preventing EACCES errors in CI/test environments.
|
||||||
console.error('Failed to create agent directory for API logs', e);
|
*/
|
||||||
|
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(
|
/** Lazily-initialized API logger instance. */
|
||||||
winston.format.timestamp(),
|
export const apiLogger = new Proxy({} as winston.Logger, {
|
||||||
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
get(_target, prop: string) {
|
||||||
const logEntry = {
|
const logger = getLogger();
|
||||||
timestamp,
|
const value = logger[prop as keyof winston.Logger];
|
||||||
level,
|
if (typeof value === 'function') {
|
||||||
message,
|
return value.bind(logger);
|
||||||
...meta,
|
}
|
||||||
};
|
return value;
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user