@mrtkrcm/acp-claude-code
Version:
ACP (Agent Client Protocol) bridge for Claude Code
139 lines • 5.16 kB
JavaScript
import { createWriteStream } from 'node:fs';
import { resolve } from 'node:path';
export class Logger {
component;
debugMode;
fileLogger = null;
logBuffer = [];
flushTimer;
BUFFER_SIZE = 50;
MAX_BUFFER_SIZE = 200; // Prevent memory leaks
FLUSH_INTERVAL = 5000; // 5 seconds
constructor(component, debugMode = false) {
this.component = component;
this.debugMode = debugMode;
this.initializeFileLogging();
}
initializeFileLogging() {
const logFile = process.env.ACP_LOG_FILE;
if (!logFile)
return;
try {
const logPath = resolve(logFile);
this.fileLogger = createWriteStream(logPath, { flags: 'a' });
this.fileLogger.on('error', (error) => {
console.error(`[${this.component}] Log file error: ${error.message}`);
this.fileLogger = null;
});
}
catch (error) {
console.error(`[${this.component}] Failed to create log file: ${error}`);
}
}
log(message, level = 'DEBUG', contextOrArgs, ...args) {
// Handle flexible parameter types for backwards compatibility
let context;
let allArgs = args;
if (typeof contextOrArgs === 'object' && contextOrArgs !== null && !Array.isArray(contextOrArgs)) {
try {
context = contextOrArgs;
}
catch {
allArgs = [contextOrArgs, ...args];
}
}
else if (contextOrArgs !== undefined) {
allArgs = [contextOrArgs, ...args];
}
const entry = {
timestamp: new Date().toISOString(),
level,
component: this.component,
message,
context,
args: allArgs.length > 0 ? allArgs.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)) : undefined
};
const formattedMessage = `[${entry.timestamp}] [${level}] [${this.component}] ${message}`;
const argsStr = entry.args?.length ? ` ${entry.args.join(' ')}` : '';
// Console output based on level and debug setting
if (this.debugMode || level !== 'DEBUG') {
const consoleMethod = this.getConsoleMethod(level);
consoleMethod(formattedMessage + argsStr);
}
// File logging with buffering and overflow protection
if (this.fileLogger) {
// Prevent buffer overflow by removing old entries
if (this.logBuffer.length >= this.MAX_BUFFER_SIZE) {
this.logBuffer.shift(); // Remove oldest entry
}
this.logBuffer.push(entry);
// Immediate flush for errors or if buffer is full
if (level === 'ERROR' || this.logBuffer.length >= this.BUFFER_SIZE) {
this.flushLogBuffer();
}
else if (!this.flushTimer) {
// Schedule flush
this.flushTimer = setTimeout(() => this.flushLogBuffer(), this.FLUSH_INTERVAL);
}
}
}
getConsoleMethod(level) {
switch (level) {
case 'ERROR': return console.error;
case 'WARN': return console.warn;
default: return console.log;
}
}
debug(message, contextOrArgs, ...args) {
this.log(message, 'DEBUG', contextOrArgs, ...args);
}
info(message, contextOrArgs, ...args) {
this.log(message, 'INFO', contextOrArgs, ...args);
}
warn(message, contextOrArgs, ...args) {
this.log(message, 'WARN', contextOrArgs, ...args);
}
error(message, contextOrArgs, ...args) {
this.log(message, 'ERROR', contextOrArgs, ...args);
}
writeStartupMessage() {
if (this.fileLogger) {
this.fileLogger.write(`\n=== ${this.component} Started at ${new Date().toISOString()} ===\n`);
}
}
writeShutdownMessage() {
if (this.fileLogger) {
this.fileLogger.write(`=== ${this.component} Stopped at ${new Date().toISOString()} ===\n`);
this.fileLogger.end();
}
}
flushLogBuffer() {
if (this.logBuffer.length === 0)
return;
if (this.fileLogger) {
const entries = this.logBuffer.splice(0); // Clear buffer
const logText = entries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
try {
this.fileLogger.write(logText);
}
catch (error) {
// If write fails, clear buffer to prevent memory leak
console.error(`[${this.component}] Failed to write log buffer: ${error}`);
}
}
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = undefined;
}
}
destroy() {
// Flush any remaining logs
this.flushLogBuffer();
this.writeShutdownMessage();
}
}
// Factory function for consistent logger creation
export function createLogger(component) {
return new Logger(component, process.env.ACP_DEBUG === 'true');
}
//# sourceMappingURL=logger.js.map