UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

253 lines 8.38 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; constructor(config = { level: "info", format: "text", 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: "text", 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") { void 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