claude-flow-tbowman01
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
252 lines • 8.39 kB
JavaScript
/**
* 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