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