UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

522 lines 17.7 kB
/** * Advanced Logging System with Structured Format and Levels * * Provides comprehensive logging with structured data, multiple outputs, * log aggregation, security logging, and performance monitoring. */ import { writeFile, mkdir, readdir, stat, unlink } from 'fs/promises'; import { join } from 'path'; import { homedir } from 'os'; import chalk from 'chalk'; // Enhanced log levels with numeric priorities export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["TRACE"] = 0] = "TRACE"; LogLevel[LogLevel["DEBUG"] = 1] = "DEBUG"; LogLevel[LogLevel["INFO"] = 2] = "INFO"; LogLevel[LogLevel["WARN"] = 3] = "WARN"; LogLevel[LogLevel["ERROR"] = 4] = "ERROR"; LogLevel[LogLevel["FATAL"] = 5] = "FATAL"; })(LogLevel || (LogLevel = {})); // Log categories for better organization export var LogCategory; (function (LogCategory) { LogCategory["SYSTEM"] = "system"; LogCategory["SECURITY"] = "security"; LogCategory["PERFORMANCE"] = "performance"; LogCategory["AGENT"] = "agent"; LogCategory["TOOL"] = "tool"; LogCategory["MCP"] = "mcp"; LogCategory["USER"] = "user"; LogCategory["MODEL"] = "model"; LogCategory["FILE_SYSTEM"] = "file_system"; LogCategory["NETWORK"] = "network"; LogCategory["API"] = "api"; LogCategory["ERROR"] = "error"; LogCategory["AUDIT"] = "audit"; })(LogCategory || (LogCategory = {})); /** * Advanced Structured Logger */ export class AdvancedLogger { config; logQueue = []; isProcessing = false; correlationCounter = 0; performanceMetrics = []; activeOperations = new Map(); constructor(config = {}) { this.config = { level: LogLevel.INFO, categories: Object.values(LogCategory), outputs: [ { type: 'console', config: { colors: true }, formatter: new ConsoleFormatter(), filter: new LevelFilter(LogLevel.INFO), }, { type: 'file', config: { path: join(homedir(), '.codecrucible', 'logs'), filename: 'codecrucible-structured.log', }, formatter: new JSONFormatter(), filter: new LevelFilter(LogLevel.DEBUG), }, ], retention: { maxFiles: 10, maxAge: 30, maxSize: 100 * 1024 * 1024, // 100MB }, security: { enableSecurityLogging: true, enableAuditLogging: true, maskSensitiveData: true, sensitiveFields: ['password', 'token', 'key', 'secret', 'api_key', 'auth'], }, performance: { enablePerformanceLogging: true, slowOperationThreshold: 1000, enableMemoryTracking: true, }, correlation: { enableCorrelation: true, correlationIdHeader: 'X-Correlation-ID', }, ...config, }; } /** * Log with structured data */ log(level, category, message, context, metadata, error) { if (!this.shouldLog(level, category)) { return; } const entry = { timestamp: new Date().toISOString(), level, category, message, context: this.sanitizeContext(context), metadata: { ...metadata, }, correlationId: metadata?.requestId || this.generateCorrelationId(), error: error ? this.serializeError(error) : undefined, sensitive: this.detectSensitiveData(message, context), }; this.enqueueLog(entry); } /** * Trace level logging (most verbose) */ trace(category, message, context, metadata) { this.log(LogLevel.TRACE, category, message, context, metadata); } /** * Debug level logging */ debug(category, message, context, metadata) { this.log(LogLevel.DEBUG, category, message, context, metadata); } /** * Info level logging */ info(category, message, context, metadata) { this.log(LogLevel.INFO, category, message, context, metadata); } /** * Warning level logging */ warn(category, message, context, metadata) { this.log(LogLevel.WARN, category, message, context, metadata); } /** * Error level logging */ error(category, message, error, context, metadata) { this.log(LogLevel.ERROR, category, message, context, metadata, error); } /** * Fatal level logging (highest severity) */ fatal(category, message, error, context, metadata) { this.log(LogLevel.FATAL, category, message, context, metadata, error); } /** * Security-specific logging */ security(message, context, metadata) { if (this.config.security.enableSecurityLogging) { this.log(LogLevel.WARN, LogCategory.SECURITY, message, context, { ...metadata, tags: [...(metadata?.tags || []), 'security'], }); } } /** * Audit logging for compliance */ audit(message, context, metadata) { if (this.config.security.enableAuditLogging) { this.log(LogLevel.INFO, LogCategory.AUDIT, message, context, { ...metadata, tags: [...(metadata?.tags || []), 'audit'], }); } } /** * Performance logging */ performance(operation, duration, context) { if (this.config.performance.enablePerformanceLogging) { const metrics = { operation, duration, memory: this.getMemoryUsage(), timestamp: new Date().toISOString(), context, }; this.performanceMetrics.push(metrics); // Log slow operations if (duration > this.config.performance.slowOperationThreshold) { this.warn(LogCategory.PERFORMANCE, `Slow operation detected: ${operation}`, { duration, threshold: this.config.performance.slowOperationThreshold, memory: metrics.memory, ...context, }); } // Keep only recent metrics - reduced to prevent memory pressure if (this.performanceMetrics.length > 100) { this.performanceMetrics = this.performanceMetrics.slice(-50); } } } /** * Start performance tracking for an operation */ startOperation(operationId, context) { this.activeOperations.set(operationId, { start: Date.now(), context, }); } /** * End performance tracking and log results */ endOperation(operationId, additionalContext) { const operation = this.activeOperations.get(operationId); if (operation) { const duration = Date.now() - operation.start; this.performance(operationId, duration, { ...operation.context, ...additionalContext, }); this.activeOperations.delete(operationId); } } /** * Create a child logger with additional context */ child(context) { const childLogger = new AdvancedLogger(this.config); // Override log method to include context const originalLog = childLogger.log.bind(childLogger); childLogger.log = (level, category, message, childContext, metadata, error) => { return originalLog(level, category, message, { ...context, ...childContext }, metadata, error); }; return childLogger; } /** * Get performance statistics */ getPerformanceStats() { const total = this.performanceMetrics.length; const avgDuration = total > 0 ? this.performanceMetrics.reduce((sum, m) => sum + m.duration, 0) / total : 0; const slowOps = this.performanceMetrics.filter(m => m.duration > this.config.performance.slowOperationThreshold).length; return { totalOperations: total, averageDuration: avgDuration, slowOperations: slowOps, memoryUsage: this.getMemoryUsage(), recentMetrics: this.performanceMetrics.slice(-10), }; } /** * Clear performance metrics */ clearPerformanceMetrics() { this.performanceMetrics = []; } /** * Flush all pending logs */ async flush() { while (this.logQueue.length > 0 || this.isProcessing) { await new Promise(resolve => setTimeout(resolve, 10)); } } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; } shouldLog(level, category) { return level >= this.config.level && this.config.categories.includes(category); } sanitizeContext(context) { if (!context || !this.config.security.maskSensitiveData) { return context; } const sanitized = { ...context }; for (const field of this.config.security.sensitiveFields) { this.maskSensitiveField(sanitized, field); } return sanitized; } maskSensitiveField(obj, fieldName) { if (typeof obj === 'object' && obj !== null) { for (const key of Object.keys(obj)) { if (key.toLowerCase().includes(fieldName.toLowerCase())) { obj[key] = '[MASKED]'; } else if (typeof obj[key] === 'object') { this.maskSensitiveField(obj[key], fieldName); } } } } detectSensitiveData(message, context) { const sensitivePatterns = [ /password/i, /token/i, /secret/i, /api[_-]?key/i, /auth/i, /credential/i, ]; const textToCheck = message + (context ? JSON.stringify(context) : ''); return sensitivePatterns.some(pattern => pattern.test(textToCheck)); } serializeError(error) { return { name: error.name, message: error.message, stack: error.stack, code: error.code, }; } generateCorrelationId() { if (this.config.correlation.enableCorrelation) { return `${Date.now()}-${++this.correlationCounter}`; } return ''; } getMemoryUsage() { if (this.config.performance.enableMemoryTracking) { return process.memoryUsage(); } return null; } enqueueLog(entry) { this.logQueue.push(entry); this.processLogQueue(); } async processLogQueue() { if (this.isProcessing || this.logQueue.length === 0) { return; } this.isProcessing = true; try { const entries = this.logQueue.splice(0, 50); // Process in batches await Promise.all(this.config.outputs.map(output => this.processOutput(output, entries))); } catch (error) { console.error('Failed to process log queue:', error); } finally { this.isProcessing = false; // Process remaining queue if any if (this.logQueue.length > 0) { setTimeout(() => this.processLogQueue(), 10); } } } async processOutput(output, entries) { const filteredEntries = entries.filter(entry => !output.filter || output.filter.shouldLog(entry)); if (filteredEntries.length === 0) { return; } switch (output.type) { case 'console': this.outputToConsole(output, filteredEntries); break; case 'file': await this.outputToFile(output, filteredEntries); break; case 'remote': await this.outputToRemote(output, filteredEntries); break; default: console.warn(`Unknown output type: ${output.type}`); } } outputToConsole(output, entries) { for (const entry of entries) { const formatted = output.formatter?.format(entry) || this.defaultConsoleFormat(entry); console.log(formatted); } } async outputToFile(output, entries) { try { const { path, filename } = output.config; await mkdir(path, { recursive: true }); const logFile = join(path, filename); const content = entries.map(entry => output.formatter?.format(entry) || JSON.stringify(entry)).join('\n') + '\n'; await writeFile(logFile, content, { flag: 'a' }); // Manage log retention await this.manageLogRetention(path); } catch (error) { console.error('Failed to write to log file:', error); } } async outputToRemote(output, entries) { // Implementation for remote logging (e.g., to log aggregation service) // This would send logs to external services like ELK, Splunk, etc. console.log('Remote logging not implemented yet'); } defaultConsoleFormat(entry) { const timestamp = chalk.gray(entry.timestamp); const level = this.getColoredLevel(entry.level); const category = chalk.cyan(`[${entry.category}]`); let message = `${timestamp} ${level} ${category} ${entry.message}`; if (entry.context) { message += `\n${chalk.gray(JSON.stringify(entry.context, null, 2))}`; } if (entry.error) { message += `\n${chalk.red(entry.error.stack || entry.error.message)}`; } return message; } getColoredLevel(level) { const levelName = LogLevel[level].padEnd(5); switch (level) { case LogLevel.TRACE: return chalk.gray(levelName); case LogLevel.DEBUG: return chalk.blue(levelName); case LogLevel.INFO: return chalk.green(levelName); case LogLevel.WARN: return chalk.yellow(levelName); case LogLevel.ERROR: return chalk.red(levelName); case LogLevel.FATAL: return chalk.magenta(levelName); default: return levelName; } } async manageLogRetention(logPath) { try { const files = await readdir(logPath); const logFiles = files.filter(f => f.endsWith('.log')); // Remove old files const now = Date.now(); const maxAge = this.config.retention.maxAge * 24 * 60 * 60 * 1000; for (const file of logFiles) { const filePath = join(logPath, file); const stats = await stat(filePath); if (now - stats.mtime.getTime() > maxAge) { await unlink(filePath); } } // Remove excess files if too many if (logFiles.length > this.config.retention.maxFiles) { const filesToRemove = logFiles .sort() .slice(0, logFiles.length - this.config.retention.maxFiles); for (const file of filesToRemove) { await unlink(join(logPath, file)); } } } catch (error) { console.warn('Failed to manage log retention:', error); } } } /** * Console formatter for human-readable output */ export class ConsoleFormatter { format(entry) { const timestamp = chalk.gray(entry.timestamp); const level = this.getColoredLevel(entry.level); const category = chalk.cyan(`[${entry.category}]`); return `${timestamp} ${level} ${category} ${entry.message}`; } getColoredLevel(level) { const levelName = LogLevel[level].padEnd(5); switch (level) { case LogLevel.TRACE: return chalk.gray(levelName); case LogLevel.DEBUG: return chalk.blue(levelName); case LogLevel.INFO: return chalk.green(levelName); case LogLevel.WARN: return chalk.yellow(levelName); case LogLevel.ERROR: return chalk.red(levelName); case LogLevel.FATAL: return chalk.magenta(levelName); default: return levelName; } } } /** * JSON formatter for structured output */ export class JSONFormatter { format(entry) { return JSON.stringify(entry); } } /** * Level-based filter */ export class LevelFilter { minLevel; constructor(minLevel) { this.minLevel = minLevel; } shouldLog(entry) { return entry.level >= this.minLevel; } } /** * Category-based filter */ export class CategoryFilter { allowedCategories; constructor(allowedCategories) { this.allowedCategories = allowedCategories; } shouldLog(entry) { return this.allowedCategories.includes(entry.category); } } // Create default advanced logger instance export const advancedLogger = new AdvancedLogger(); // Export main class for use elsewhere //# sourceMappingURL=advanced-logging-system.js.map