UNPKG

codecrucible-synth

Version:

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

305 lines 10.4 kB
/** * Enterprise Structured Logging System * Implements correlation IDs, structured JSON logs, and multiple transport layers */ import winston from 'winston'; import crypto from 'crypto'; import { AsyncLocalStorage } from 'async_hooks'; // Context storage for correlation IDs const correlationStorage = new AsyncLocalStorage(); export class StructuredLogger { logger; config; service; version; constructor(config) { this.config = config; this.service = config.service; this.version = config.version; this.logger = winston.createLogger({ level: config.level, format: this.createFormat(), defaultMeta: { service: this.service, version: this.version, environment: config.environment, }, transports: this.createTransports(), }); // Handle uncaught exceptions and unhandled rejections this.logger.exceptions.handle(new winston.transports.File({ filename: 'exceptions.log' })); this.logger.rejections.handle(new winston.transports.File({ filename: 'rejections.log' })); } /** * Create Winston format with structured logging */ createFormat() { return winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json(), winston.format.printf(({ timestamp, level, message, service, version, ...meta }) => { const correlationId = this.getCorrelationId(); const logEntry = { timestamp: timestamp, level: level, message: message, service: service, version: version, correlationId, metadata: meta, }; // Add tracing information if available if (meta.traceId) logEntry.traceId = meta.traceId; if (meta.spanId) logEntry.spanId = meta.spanId; if (meta.userId) logEntry.userId = meta.userId; if (meta.sessionId) logEntry.sessionId = meta.sessionId; if (meta.operation) logEntry.operation = meta.operation; if (meta.duration) logEntry.duration = meta.duration; if (meta.statusCode) logEntry.statusCode = meta.statusCode; // Handle error objects if (meta.error && meta.error instanceof Error) { logEntry.error = { name: meta.error.name, message: meta.error.message, stack: meta.error.stack || '', code: meta.error.code, }; } return JSON.stringify(logEntry); })); } /** * Create Winston transports based on configuration */ createTransports() { const transports = []; // Console transport if (this.config.outputs.console) { transports.push(new winston.transports.Console({ format: this.config.format === 'text' ? winston.format.combine(winston.format.colorize(), winston.format.simple()) : winston.format.json(), })); } // File transport if (this.config.outputs.file) { transports.push(new winston.transports.File({ filename: 'application.log', maxsize: this.parseSize(this.config.rotation.maxSize), maxFiles: this.config.rotation.maxFiles, tailable: true, })); // Separate error log transports.push(new winston.transports.File({ filename: 'error.log', level: 'error', maxsize: this.parseSize(this.config.rotation.maxSize), maxFiles: this.config.rotation.maxFiles, tailable: true, })); } // Remote transport (e.g., ELK, Splunk, DataDog) if (this.config.outputs.remote) { // In production, add HTTP transport to send logs to centralized system transports.push(new winston.transports.Http({ host: new URL(this.config.outputs.remote.endpoint).hostname, port: parseInt(new URL(this.config.outputs.remote.endpoint).port) || 443, path: new URL(this.config.outputs.remote.endpoint).pathname, ssl: this.config.outputs.remote.endpoint.startsWith('https'), headers: { Authorization: `Bearer ${this.config.outputs.remote.apiKey}`, 'Content-Type': 'application/json', }, })); } return transports; } /** * Parse size string to bytes */ parseSize(size) { const units = { k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024 }; const match = size.toLowerCase().match(/^(\d+)([kmg]?)b?$/); if (!match) return 10 * 1024 * 1024; // Default 10MB const value = parseInt(match[1]); const unit = match[2]; return value * (units[unit] || 1); } /** * Get or generate correlation ID */ getCorrelationId() { return correlationStorage.getStore() || crypto.randomUUID(); } /** * Set correlation ID for async context */ setCorrelationId(id) { correlationStorage.enterWith(id); } /** * Generate new correlation ID */ generateCorrelationId() { const id = crypto.randomUUID(); this.setCorrelationId(id); return id; } /** * Run function with correlation ID context */ withCorrelationId(id, fn) { return correlationStorage.run(id, fn); } /** * Structured logging methods */ info(message, metadata = {}) { this.logger.info(message, metadata); } warn(message, metadata = {}) { this.logger.warn(message, metadata); } error(message, error, metadata = {}) { this.logger.error(message, { ...metadata, error }); } debug(message, metadata = {}) { this.logger.debug(message, metadata); } /** * Operation-specific logging with timing */ async logOperation(operation, fn, metadata = {}) { const startTime = Date.now(); const operationId = crypto.randomUUID(); this.info(`Operation started: ${operation}`, { operation, operationId, ...metadata, }); try { const result = await fn(); const duration = Date.now() - startTime; this.info(`Operation completed: ${operation}`, { operation, operationId, duration, status: 'success', ...metadata, }); return result; } catch (error) { const duration = Date.now() - startTime; this.error(`Operation failed: ${operation}`, error, { operation, operationId, duration, status: 'error', ...metadata, }); throw error; } } /** * HTTP request logging */ logHttpRequest(req, res, duration) { this.info('HTTP request processed', { method: req.method, url: req.originalUrl, statusCode: res.statusCode, duration, userAgent: req.get('User-Agent'), ip: req.ip, userId: req.user?.userId, sessionId: req.sessionId, }); } /** * Security event logging */ logSecurityEvent(event, metadata = {}) { this.warn(`Security event: ${event}`, { securityEvent: event, timestamp: new Date().toISOString(), ...metadata, }); } /** * Performance metrics logging */ logPerformanceMetric(metric, value, unit, metadata = {}) { this.info(`Performance metric: ${metric}`, { metric, value, unit, timestamp: new Date().toISOString(), ...metadata, }); } /** * Business event logging */ logBusinessEvent(event, metadata = {}) { this.info(`Business event: ${event}`, { businessEvent: event, timestamp: new Date().toISOString(), ...metadata, }); } /** * Express middleware for request logging */ middleware() { return (req, res, next) => { // Generate correlation ID for request const correlationId = req.headers['x-correlation-id'] || this.generateCorrelationId(); req.correlationId = correlationId; res.setHeader('X-Correlation-ID', correlationId); const startTime = Date.now(); // Log request this.info('HTTP request received', { method: req.method, url: req.originalUrl, userAgent: req.get('User-Agent'), ip: req.ip, correlationId, }); // Override res.end to log response const originalEnd = res.end; res.end = function (chunk, encoding) { const duration = Date.now() - startTime; // Use the logger instance through closure req.logger.logHttpRequest(req, res, duration); originalEnd.call(this, chunk, encoding); }; // Attach logger to request for use in route handlers req.logger = this; this.withCorrelationId(correlationId, () => next()); }; } /** * Child logger with additional context */ child(metadata) { const childLogger = Object.create(this); childLogger.logger = this.logger.child(metadata); return childLogger; } /** * Flush logs (useful for graceful shutdown) */ async flush() { return new Promise(resolve => { this.logger.on('finish', resolve); this.logger.end(); }); } } //# sourceMappingURL=structured-logger.js.map