codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
739 lines (652 loc) • 19.2 kB
text/typescript
/**
* 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, access, readdir, stat, unlink } from 'fs/promises';
import { join, dirname } from 'path';
import { homedir } from 'os';
import chalk from 'chalk';
import { logger as baseLogger } from '../logger.js';
// Enhanced log levels with numeric priorities
export enum LogLevel {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5,
}
// Log categories for better organization
export enum LogCategory {
SYSTEM = 'system',
SECURITY = 'security',
PERFORMANCE = 'performance',
AGENT = 'agent',
TOOL = 'tool',
MCP = 'mcp',
USER = 'user',
MODEL = 'model',
FILE_SYSTEM = 'file_system',
NETWORK = 'network',
API = 'api',
ERROR = 'error',
AUDIT = 'audit',
}
// Structured log entry interface
export interface StructuredLogEntry {
timestamp: string;
level: LogLevel;
category: LogCategory;
message: string;
context?: Record<string, any>;
metadata?: {
requestId?: string;
userId?: string;
sessionId?: string;
source?: string;
component?: string;
operation?: string;
duration?: number;
tags?: string[];
};
error?: {
name: string;
message: string;
stack?: string;
code?: string | number;
};
sensitive?: boolean;
correlationId?: string;
}
// Advanced logger configuration
export interface AdvancedLoggerConfig {
level: LogLevel;
categories: LogCategory[];
outputs: LogOutput[];
retention: {
maxFiles: number;
maxAge: number; // days
maxSize: number; // bytes
};
security: {
enableSecurityLogging: boolean;
enableAuditLogging: boolean;
maskSensitiveData: boolean;
sensitiveFields: string[];
};
performance: {
enablePerformanceLogging: boolean;
slowOperationThreshold: number; // ms
enableMemoryTracking: boolean;
};
correlation: {
enableCorrelation: boolean;
correlationIdHeader: string;
};
}
// Log output configurations
export interface LogOutput {
type: 'console' | 'file' | 'remote' | 'database';
config: any;
formatter?: LogFormatter;
filter?: LogFilter;
}
export interface LogFormatter {
format(entry: StructuredLogEntry): string;
}
export interface LogFilter {
shouldLog(entry: StructuredLogEntry): boolean;
}
// Performance metrics interface
export interface PerformanceMetrics {
operation: string;
duration: number;
memory: {
used: number;
total: number;
external: number;
};
timestamp: string;
context?: Record<string, any>;
}
/**
* Advanced Structured Logger
*/
export class AdvancedLogger {
private config: AdvancedLoggerConfig;
private logQueue: StructuredLogEntry[] = [];
private isProcessing = false;
private correlationCounter = 0;
private performanceMetrics: PerformanceMetrics[] = [];
private activeOperations = new Map<string, { start: number; context?: any }>();
constructor(config: Partial<AdvancedLoggerConfig> = {}) {
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: LogLevel,
category: LogCategory,
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata'],
error?: Error
): void {
if (!this.shouldLog(level, category)) {
return;
}
const entry: StructuredLogEntry = {
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: LogCategory,
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.TRACE, category, message, context, metadata);
}
/**
* Debug level logging
*/
debug(
category: LogCategory,
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.DEBUG, category, message, context, metadata);
}
/**
* Info level logging
*/
info(
category: LogCategory,
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.INFO, category, message, context, metadata);
}
/**
* Warning level logging
*/
warn(
category: LogCategory,
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.WARN, category, message, context, metadata);
}
/**
* Error level logging
*/
error(
category: LogCategory,
message: string,
error?: Error,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.ERROR, category, message, context, metadata, error);
}
/**
* Fatal level logging (highest severity)
*/
fatal(
category: LogCategory,
message: string,
error?: Error,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
this.log(LogLevel.FATAL, category, message, context, metadata, error);
}
/**
* Security-specific logging
*/
security(
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
if (this.config.security.enableSecurityLogging) {
this.log(LogLevel.WARN, LogCategory.SECURITY, message, context, {
...metadata,
tags: [...(metadata?.tags || []), 'security'],
});
}
}
/**
* Audit logging for compliance
*/
audit(
message: string,
context?: Record<string, any>,
metadata?: StructuredLogEntry['metadata']
): void {
if (this.config.security.enableAuditLogging) {
this.log(LogLevel.INFO, LogCategory.AUDIT, message, context, {
...metadata,
tags: [...(metadata?.tags || []), 'audit'],
});
}
}
/**
* Performance logging
*/
performance(operation: string, duration: number, context?: Record<string, any>): void {
if (this.config.performance.enablePerformanceLogging) {
const metrics: PerformanceMetrics = {
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: string, context?: any): void {
this.activeOperations.set(operationId, {
start: Date.now(),
context,
});
}
/**
* End performance tracking and log results
*/
endOperation(operationId: string, additionalContext?: any): void {
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: Record<string, any>): AdvancedLogger {
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(): {
totalOperations: number;
averageDuration: number;
slowOperations: number;
memoryUsage: any;
recentMetrics: PerformanceMetrics[];
} {
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(): void {
this.performanceMetrics = [];
}
/**
* Flush all pending logs
*/
async flush(): Promise<void> {
while (this.logQueue.length > 0 || this.isProcessing) {
await new Promise(resolve => setTimeout(resolve, 10));
}
}
/**
* Update configuration
*/
updateConfig(config: Partial<AdvancedLoggerConfig>): void {
this.config = { ...this.config, ...config };
}
private shouldLog(level: LogLevel, category: LogCategory): boolean {
return level >= this.config.level && this.config.categories.includes(category);
}
private sanitizeContext(context?: Record<string, any>): Record<string, any> | undefined {
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;
}
private maskSensitiveField(obj: any, fieldName: string): void {
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);
}
}
}
}
private detectSensitiveData(message: string, context?: Record<string, any>): boolean {
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));
}
private serializeError(error: Error): StructuredLogEntry['error'] {
return {
name: error.name,
message: error.message,
stack: error.stack,
code: (error as any).code,
};
}
private generateCorrelationId(): string {
if (this.config.correlation.enableCorrelation) {
return `${Date.now()}-${++this.correlationCounter}`;
}
return '';
}
private getMemoryUsage(): any {
if (this.config.performance.enableMemoryTracking) {
return process.memoryUsage();
}
return null;
}
private enqueueLog(entry: StructuredLogEntry): void {
this.logQueue.push(entry);
this.processLogQueue();
}
private async processLogQueue(): Promise<void> {
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);
}
}
}
private async processOutput(output: LogOutput, entries: StructuredLogEntry[]): Promise<void> {
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}`);
}
}
private outputToConsole(output: LogOutput, entries: StructuredLogEntry[]): void {
for (const entry of entries) {
const formatted = output.formatter?.format(entry) || this.defaultConsoleFormat(entry);
console.log(formatted);
}
}
private async outputToFile(output: LogOutput, entries: StructuredLogEntry[]): Promise<void> {
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);
}
}
private async outputToRemote(output: LogOutput, entries: StructuredLogEntry[]): Promise<void> {
// 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');
}
private defaultConsoleFormat(entry: StructuredLogEntry): string {
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;
}
private getColoredLevel(level: LogLevel): string {
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;
}
}
private async manageLogRetention(logPath: string): Promise<void> {
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 implements LogFormatter {
format(entry: StructuredLogEntry): string {
const timestamp = chalk.gray(entry.timestamp);
const level = this.getColoredLevel(entry.level);
const category = chalk.cyan(`[${entry.category}]`);
return `${timestamp} ${level} ${category} ${entry.message}`;
}
private getColoredLevel(level: LogLevel): string {
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 implements LogFormatter {
format(entry: StructuredLogEntry): string {
return JSON.stringify(entry);
}
}
/**
* Level-based filter
*/
export class LevelFilter implements LogFilter {
constructor(private minLevel: LogLevel) {}
shouldLog(entry: StructuredLogEntry): boolean {
return entry.level >= this.minLevel;
}
}
/**
* Category-based filter
*/
export class CategoryFilter implements LogFilter {
constructor(private allowedCategories: LogCategory[]) {}
shouldLog(entry: StructuredLogEntry): boolean {
return this.allowedCategories.includes(entry.category);
}
}
// Create default advanced logger instance
export const advancedLogger = new AdvancedLogger();
// Export main class for use elsewhere