/** * Structured Logging System * Agent 4: Production Deployment * * Provides structured JSON logging with support for different log levels, * context enrichment, and production-ready formatting. */ import { env, LogLevel } from '../config'; interface LogContext { [key: string]: unknown; } interface LogEntry { timestamp: string; level: LogLevel; message: string; service: string; environment: string; context?: LogContext; error?: { name: string; message: string; stack?: string; }; } type LogMethod = (message: string, context?: LogContext) => void; interface Logger { error: (message: string, errorOrContext?: Error | LogContext, context?: LogContext) => void; warn: LogMethod; info: LogMethod; debug: LogMethod; trace: LogMethod; child: (context: LogContext) => Logger; } const LOG_LEVEL_PRIORITY: Record = { error: 0, warn: 1, info: 2, debug: 3, trace: 4, }; const LOG_LEVEL_COLORS: Record = { error: '\x1b[31m', // Red warn: '\x1b[33m', // Yellow info: '\x1b[36m', // Cyan debug: '\x1b[35m', // Magenta trace: '\x1b[90m', // Gray }; const RESET_COLOR = '\x1b[0m'; class LoggerImpl implements Logger { private baseContext: LogContext; private minLevel: number; private format: 'json' | 'pretty'; private serviceName: string; constructor(context: LogContext = {}) { this.baseContext = context; this.minLevel = LOG_LEVEL_PRIORITY[env.logLevel]; this.format = env.logFormat; this.serviceName = env.appName; } private shouldLog(level: LogLevel): boolean { return LOG_LEVEL_PRIORITY[level] <= this.minLevel; } private formatEntry(entry: LogEntry): string { if (this.format === 'pretty') { return this.formatPretty(entry); } return JSON.stringify(entry); } private formatPretty(entry: LogEntry): string { const color = LOG_LEVEL_COLORS[entry.level]; const timestamp = new Date(entry.timestamp).toLocaleTimeString(); const level = entry.level.toUpperCase().padEnd(5); let output = `${color}[${timestamp}] ${level}${RESET_COLOR} ${entry.message}`; if (entry.context && Object.keys(entry.context).length > 0) { output += ` ${JSON.stringify(entry.context)}`; } if (entry.error) { output += `\n ${color}Error: ${entry.error.message}${RESET_COLOR}`; if (entry.error.stack) { output += `\n${entry.error.stack.split('\n').slice(1).join('\n')}`; } } return output; } private log(level: LogLevel, message: string, context?: LogContext, error?: Error): void { if (!this.shouldLog(level)) { return; } const entry: LogEntry = { timestamp: new Date().toISOString(), level, message, service: this.serviceName, environment: env.nodeEnv, }; // Merge base context with provided context const mergedContext = { ...this.baseContext, ...context }; if (Object.keys(mergedContext).length > 0) { entry.context = mergedContext; } // Add error details if present if (error) { entry.error = { name: error.name, message: error.message, stack: error.stack, }; } const output = this.formatEntry(entry); // Use appropriate console method switch (level) { case 'error': console.error(output); break; case 'warn': console.warn(output); break; case 'debug': case 'trace': console.debug(output); break; default: console.log(output); } } error(message: string, errorOrContext?: Error | LogContext, context?: LogContext): void { if (errorOrContext instanceof Error) { this.log('error', message, context, errorOrContext); } else { this.log('error', message, errorOrContext); } } warn(message: string, context?: LogContext): void { this.log('warn', message, context); } info(message: string, context?: LogContext): void { this.log('info', message, context); } debug(message: string, context?: LogContext): void { this.log('debug', message, context); } trace(message: string, context?: LogContext): void { this.log('trace', message, context); } child(context: LogContext): Logger { return new LoggerImpl({ ...this.baseContext, ...context }); } } // Create and export default logger instance export const logger = new LoggerImpl(); // Export for creating child loggers with context export function createLogger(context: LogContext): Logger { return new LoggerImpl(context); } // Export types export type { Logger, LogContext, LogEntry };