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
JavaScript
/**
* 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