UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

252 lines 8.39 kB
/** * Logging infrastructure for Claude-Flow */ import { promises as fs } from 'node:fs'; import * as path from 'node:path'; import { Buffer } from 'node:buffer'; import process from 'node:process'; export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["WARN"] = 2] = "WARN"; LogLevel[LogLevel["ERROR"] = 3] = "ERROR"; })(LogLevel || (LogLevel = {})); /** * Logger implementation with context support */ export class Logger { static instance; config; context; fileHandle; currentFileSize = 0; currentFileIndex = 0; isClosing = false; get level() { return this.config.level; } constructor(config = { level: 'info', format: 'json', destination: 'console', }, context = {}) { // Validate file path if file destination if ((config.destination === 'file' || config.destination === 'both') && !config.filePath) { throw new Error('File path required for file logging'); } this.config = config; this.context = context; } /** * Gets the singleton instance of the logger */ static getInstance(config) { if (!Logger.instance) { if (!config) { // Use default config if none provided and not in test environment const isTestEnv = process.env.CLAUDE_FLOW_ENV === 'test'; if (isTestEnv) { throw new Error('Logger configuration required for initialization'); } config = { level: 'info', format: 'json', destination: 'console', }; } Logger.instance = new Logger(config); } return Logger.instance; } /** * Updates logger configuration */ async configure(config) { this.config = config; // Reset file handle if destination changed if (this.fileHandle && config.destination !== 'file' && config.destination !== 'both') { await this.fileHandle.close(); delete this.fileHandle; } } debug(message, meta) { this.log(LogLevel.DEBUG, message, meta); } info(message, meta) { this.log(LogLevel.INFO, message, meta); } warn(message, meta) { this.log(LogLevel.WARN, message, meta); } error(message, error) { this.log(LogLevel.ERROR, message, undefined, error); } /** * Creates a child logger with additional context */ child(context) { return new Logger(this.config, { ...this.context, ...context }); } /** * Properly close the logger and release resources */ async close() { this.isClosing = true; if (this.fileHandle) { try { await this.fileHandle.close(); } catch (error) { console.error('Error closing log file handle:', error); } finally { delete this.fileHandle; } } } log(level, message, data, error) { if (!this.shouldLog(level)) { return; } const entry = { timestamp: new Date().toISOString(), level: LogLevel[level], message, context: this.context, data, error, }; const formatted = this.format(entry); if (this.config.destination === 'console' || this.config.destination === 'both') { this.writeToConsole(level, formatted); } if (this.config.destination === 'file' || this.config.destination === 'both') { this.writeToFile(formatted); } } shouldLog(level) { const configLevel = LogLevel[this.config.level.toUpperCase()]; return level >= configLevel; } format(entry) { if (this.config.format === 'json') { // Handle error serialization for JSON format const jsonEntry = { ...entry }; if (jsonEntry.error instanceof Error) { jsonEntry.error = { name: jsonEntry.error.name, message: jsonEntry.error.message, stack: jsonEntry.error.stack, }; } return JSON.stringify(jsonEntry); } // Text format const contextStr = Object.keys(entry.context).length > 0 ? ` ${JSON.stringify(entry.context)}` : ''; const dataStr = entry.data !== undefined ? ` ${JSON.stringify(entry.data)}` : ''; const errorStr = entry.error !== undefined ? entry.error instanceof Error ? `\n Error: ${entry.error.message}\n Stack: ${entry.error.stack}` : ` Error: ${JSON.stringify(entry.error)}` : ''; return `[${entry.timestamp}] ${entry.level} ${entry.message}${contextStr}${dataStr}${errorStr}`; } writeToConsole(level, message) { switch (level) { case LogLevel.DEBUG: console.debug(message); break; case LogLevel.INFO: console.info(message); break; case LogLevel.WARN: console.warn(message); break; case LogLevel.ERROR: console.error(message); break; } } async writeToFile(message) { if (!this.config.filePath || this.isClosing) { return; } try { // Check if we need to rotate the log file if (await this.shouldRotate()) { await this.rotate(); } // Open file handle if not already open if (!this.fileHandle) { this.fileHandle = await fs.open(this.config.filePath, 'a'); } // Write the message const data = Buffer.from(message + '\n', 'utf8'); await this.fileHandle.write(data); this.currentFileSize += data.length; } catch (error) { console.error('Failed to write to log file:', error); } } async shouldRotate() { if (!this.config.maxFileSize || !this.config.filePath) { return false; } try { const stat = await fs.stat(this.config.filePath); return stat.size >= this.config.maxFileSize; } catch { return false; } } async rotate() { if (!this.config.filePath || !this.config.maxFiles) { return; } // Close current file if (this.fileHandle) { await this.fileHandle.close(); delete this.fileHandle; } // Rename current file const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const rotatedPath = `${this.config.filePath}.${timestamp}`; await fs.rename(this.config.filePath, rotatedPath); // Clean up old files await this.cleanupOldFiles(); // Reset file size this.currentFileSize = 0; } async cleanupOldFiles() { if (!this.config.filePath || !this.config.maxFiles) { return; } const dir = path.dirname(this.config.filePath); const baseFileName = path.basename(this.config.filePath); try { const entries = await fs.readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { if (entry.isFile() && entry.name.startsWith(baseFileName + '.')) { files.push(entry.name); } } // Sort files by timestamp (newest first) files.sort().reverse(); // Remove old files const filesToRemove = files.slice(this.config.maxFiles - 1); for (const file of filesToRemove) { await fs.unlink(path.join(dir, file)); } } catch (error) { console.error('Failed to cleanup old log files:', error); } } } // Export singleton instance with lazy initialization export const logger = Logger.getInstance(); //# sourceMappingURL=logger.js.map