UNPKG

codecrucible-synth

Version:

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

428 lines 13.7 kB
import { mkdir, access } from 'fs/promises'; import { join } from 'path'; import { homedir } from 'os'; import chalk from 'chalk'; import * as api from '@opentelemetry/api'; import winston from 'winston'; /** * Enterprise Logger with Winston - 2025 Best Practices * * Features: * - Structured JSON logging for observability * - High-performance async operations * - OpenTelemetry integration * - Production-grade error handling * - Multiple transport support */ class Logger { config; winstonLogger; // Definite assignment assertion logDirectory; name; constructor(nameOrConfig, config) { // Handle overloaded constructor if (typeof nameOrConfig === 'string') { this.name = nameOrConfig; this.config = { level: 'info', toFile: true, toConsole: true, maxFileSize: '10MB', maxFiles: 5, ...config, }; } else { this.config = { level: 'info', toFile: true, toConsole: true, maxFileSize: '10MB', maxFiles: 5, ...nameOrConfig, }; } this.logDirectory = this.config.logDirectory || join(homedir(), '.codecrucible', 'logs'); this.setupWinstonLogger(); this.ensureLogDirectory(); } /** * Setup Winston logger with structured JSON format - 2025 Best Practice */ setupWinstonLogger() { // Custom JSON formatter for structured logging const jsonFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json(), winston.format.printf(({ timestamp, level, message, traceId, spanId, correlationId, userId, sessionId, ...meta }) => { const logEntry = { '@timestamp': timestamp, level: level.toUpperCase(), message, service: this.name || 'codecrucible-synth', ...(traceId ? { traceId } : {}), ...(spanId ? { spanId } : {}), ...(correlationId ? { correlationId } : {}), ...(userId ? { userId } : {}), ...(sessionId ? { sessionId } : {}), ...meta, }; return JSON.stringify(logEntry); })); // Console formatter with colors for development const consoleFormat = winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), winston.format.errors({ stack: true }), winston.format.printf(({ timestamp, level, message, traceId, ...meta }) => { let output = `${timestamp} ${level} ${message}`; if (Object.keys(meta).length > 0 && !meta.stack) { output += `\n${JSON.stringify(meta, null, 2)}`; } if (meta.stack) { output += `\n${meta.stack}`; } return output; })); const transports = []; if (this.config.toConsole) { transports.push(new winston.transports.Console({ level: this.config.level, format: consoleFormat, })); } if (this.config.toFile) { transports.push(new winston.transports.File({ filename: join(this.logDirectory, 'codecrucible.log'), level: this.config.level, format: jsonFormat, maxsize: this.parseFileSize(this.config.maxFileSize), maxFiles: this.config.maxFiles, tailable: true, })); // Separate error log file for high-priority issues transports.push(new winston.transports.File({ filename: join(this.logDirectory, 'error.log'), level: 'error', format: jsonFormat, maxsize: this.parseFileSize(this.config.maxFileSize), maxFiles: this.config.maxFiles, tailable: true, })); } this.winstonLogger = winston.createLogger({ level: this.config.level, levels: winston.config.npm.levels, transports, exitOnError: false, handleExceptions: true, handleRejections: true, }); } /** * Parse file size string to bytes */ parseFileSize(sizeStr) { const size = parseInt(sizeStr.match(/\d+/)?.[0] || '10'); const unit = sizeStr.match(/[a-zA-Z]+/)?.[0]?.toLowerCase() || 'mb'; const multipliers = { b: 1, kb: 1024, mb: 1024 * 1024, gb: 1024 * 1024 * 1024, }; return size * (multipliers[unit] || multipliers.mb); } /** * Ensure log directory exists */ async ensureLogDirectory() { try { await access(this.logDirectory); } catch { try { await mkdir(this.logDirectory, { recursive: true }); } catch (error) { console.warn(`Failed to create log directory: ${this.logDirectory}`); } } } /** * Get numeric level for comparison */ getNumericLevel(level) { const levels = { debug: 0, info: 1, warn: 2, error: 3 }; return levels[level]; } /** * Check if message should be logged based on level */ shouldLog(level) { return this.getNumericLevel(level) >= this.getNumericLevel(this.config.level); } /** * Format timestamp for display */ formatTimestamp(date) { return date.toISOString().replace('T', ' ').replace('Z', ''); } /** * Get colored prefix for console output */ getColoredPrefix(level) { const timestamp = chalk.gray(this.formatTimestamp(new Date())); switch (level) { case 'debug': return `${timestamp} ${chalk.blue('DEBUG')}`; case 'info': return `${timestamp} ${chalk.green(' INFO')}`; case 'warn': return `${timestamp} ${chalk.yellow(' WARN')}`; case 'error': return `${timestamp} ${chalk.red('ERROR')}`; } } /** * Format message for console output */ formatConsoleMessage(entry) { const prefix = this.getColoredPrefix(entry.level); let message = `${prefix} ${entry.message}`; if (entry.data) { const dataStr = typeof entry.data === 'string' ? entry.data : JSON.stringify(entry.data, null, 2); message += `\\n${chalk.gray(dataStr)}`; } if (entry.error) { message += `\\n${chalk.red(entry.error.stack || entry.error.message)}`; } return message; } /** * Format message for file output */ formatFileMessage(entry) { const timestamp = this.formatTimestamp(entry.timestamp); const level = entry.level.toUpperCase().padEnd(5); let message = `${timestamp} ${level} ${entry.message}`; if (entry.data) { const dataStr = typeof entry.data === 'string' ? entry.data : JSON.stringify(entry.data); message += ` | Data: ${dataStr}`; } if (entry.error) { message += ` | Error: ${entry.error.message}`; if (entry.error.stack) { message += `\\n${entry.error.stack}`; } } return message; } /** * Log a message with specified level - 2025 Winston Implementation */ async log(level, message, data, error) { if (!this.shouldLog(level)) { return; } // Get trace context from OpenTelemetry const span = api.trace.getActiveSpan(); const spanContext = span?.spanContext(); // Prepare metadata for Winston const metadata = {}; if (data) metadata.data = data; if (error) metadata.error = error; if (spanContext?.traceId) metadata.traceId = spanContext.traceId; if (spanContext?.spanId) metadata.spanId = spanContext.spanId; if (data?.correlationId) metadata.correlationId = data.correlationId; if (data?.userId) metadata.userId = data.userId; if (data?.sessionId) metadata.sessionId = data.sessionId; // Use Winston for structured logging - handles both console and file automatically this.winstonLogger.log(level, message, metadata); } // Note: Winston handles file operations automatically - legacy queue methods removed /** * Debug level logging */ debug(message, data) { this.log('debug', message, data); } /** * Info level logging */ info(message, data) { this.log('info', message, data); } /** * Warning level logging */ warn(message, data) { this.log('warn', message, data); } /** * Error level logging */ error(message, error, data) { if (error instanceof Error) { this.log('error', message, data, error); } else { // If second parameter is not an Error, treat it as data this.log('error', message, error); } } /** * Update logger configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Flush pending logs (Winston handles automatically) */ async flush() { // Winston handles flushing automatically return Promise.resolve(); } /** * Create a child logger with additional context */ child(context) { const childLogger = new Logger(this.config); // Override log method to include context const originalLog = childLogger.log.bind(childLogger); childLogger.log = async (level, message, data, error) => { return originalLog(level, `[${context}] ${message}`, data, error); }; return childLogger; } /** * Performance timing utility */ time(label) { const start = Date.now(); return () => { const duration = Date.now() - start; this.debug(`Timer ${label}: ${duration}ms`); }; } /** * Log performance of async operations */ async profile(label, operation) { const start = Date.now(); try { const result = await operation(); const duration = Date.now() - start; this.debug(`Operation ${label} completed in ${duration}ms`); return result; } catch (error) { const duration = Date.now() - start; this.error(`Operation ${label} failed after ${duration}ms`, error); throw error; } } /** * Log system information */ logSystemInfo() { this.info('System Information', { platform: process.platform, arch: process.arch, nodeVersion: process.version, memoryUsage: process.memoryUsage(), uptime: process.uptime(), cwd: process.cwd(), }); } /** * Audit log for compliance and security */ audit(action, details) { this.info(`AUDIT: ${action}`, { ...details, auditType: 'security', timestamp: new Date().toISOString(), }); } /** * Security event logging */ security(event, details) { this.warn(`SECURITY: ${event}`, { ...details, eventType: 'security_event', timestamp: new Date().toISOString(), }); } /** * Performance metrics logging */ metric(name, value, unit = 'ms', tags) { this.debug(`METRIC: ${name}`, { metricName: name, metricValue: value, metricUnit: unit, metricTags: tags, metricType: 'performance', timestamp: new Date().toISOString(), }); } /** * Business event logging */ business(event, details) { this.info(`BUSINESS: ${event}`, { ...details, eventType: 'business_event', timestamp: new Date().toISOString(), }); } } // Create default logger instance export const logger = new Logger(); // Export Logger class for custom instances export { Logger }; // Convenience function to update global logger config export function configureLogger(config) { logger.updateConfig(config); } // Graceful shutdown - flush logs (only register once) let loggerShutdownRegistered = false; if (!loggerShutdownRegistered) { loggerShutdownRegistered = true; process.on('beforeExit', async () => { try { await logger.flush(); } catch (error) { console.error('Failed to flush logs on exit:', error); } }); process.on('SIGINT', async () => { try { await logger.flush(); } catch (error) { console.error('Failed to flush logs on SIGINT:', error); } process.exit(0); }); process.on('SIGTERM', async () => { try { await logger.flush(); } catch (error) { console.error('Failed to flush logs on SIGTERM:', error); } process.exit(0); }); } //# sourceMappingURL=logger.js.map