SOLUTION: Challenge 1 - Singleton Logger ========================================== Challenge: Implement a Logger singleton with methods to log at different levels (info, warn, error). Ensure only one instance exists across the app. --- SOLUTION: type LogLevel = "info" | "warn" | "error" | "debug"; interface LogEntry { level: LogLevel; message: string; timestamp: Date; data?: unknown; } class Logger { private static instance: Logger | null = null; private logs: LogEntry[] = []; private logLevel: LogLevel = "info"; // Prevent instantiation private constructor() {} // Singleton access static getInstance(): Logger { if (Logger.instance === null) { Logger.instance = new Logger(); console.log("📝 Logger initialized"); } return Logger.instance; } // Set minimum log level (lower levels are filtered) setLogLevel(level: LogLevel): void { this.logLevel = level; } // Log methods info(message: string, data?: unknown): void { this.log("info", message, data); } warn(message: string, data?: unknown): void { this.log("warn", message, data); } error(message: string, data?: unknown): void { this.log("error", message, data); } debug(message: string, data?: unknown): void { this.log("debug", message, data); } // Core logging method private log(level: LogLevel, message: string, data?: unknown): void { const entry: LogEntry = { level, message, timestamp: new Date(), data }; this.logs.push(entry); // Format output const timestamp = entry.timestamp.toISOString(); const icon = this.getIcon(level); const output = data ? `${icon} [${timestamp}] ${level.toUpperCase()}: ${message} ${JSON.stringify(data)}` : `${icon} [${timestamp}] ${level.toUpperCase()}: ${message}`; // Console output this.consoleLog(level, output); } private getIcon(level: LogLevel): string { switch (level) { case "info": return "â„šī¸"; case "warn": return "âš ī¸"; case "error": return "❌"; case "debug": return "🐛"; } } private consoleLog(level: LogLevel, message: string): void { switch (level) { case "error": console.error(message); break; case "warn": console.warn(message); break; case "debug": console.debug(message); break; case "info": default: console.log(message); } } // Retrieve logs getLogs(filter?: LogLevel): LogEntry[] { if (!filter) return [...this.logs]; return this.logs.filter(log => log.level === filter); } // Clear logs clear(): void { this.logs = []; } // Get summary getSummary(): { total: number; byLevel: Record } { const byLevel: Record = { info: 0, warn: 0, error: 0, debug: 0 }; this.logs.forEach(log => { byLevel[log.level]++; }); return { total: this.logs.length, byLevel }; } } --- // USAGE: const logger = Logger.getInstance(); // Log at different levels logger.info("Application started"); logger.debug("Debug mode enabled", { version: "1.0.0" }); logger.warn("High memory usage", { mb: 512 }); logger.error("Failed to connect", { service: "database" }); console.log("\n=== Logs Summary ==="); console.log(logger.getSummary()); // Output: { total: 4, byLevel: { info: 1, warn: 1, error: 1, debug: 1 } } console.log("\n=== Error Logs Only ==="); logger.getLogs("error").forEach(log => { console.log(`${log.timestamp.toISOString()}: ${log.message}`); }); // Verify singleton const logger2 = Logger.getInstance(); console.log("\n=== Singleton Check ==="); console.log(logger === logger2); // true — same instance --- EXPLANATION: SINGLETON IMPLEMENTATION: 1. Private constructor: prevents new Logger() 2. Static instance: holds the single instance 3. getInstance(): factory method - Creates instance on first call - Returns same instance on subsequent calls LOG LEVELS: Different severity levels allow filtering: - debug: detailed info for development - info: general information - warn: warning messages (non-critical issues) - error: errors (critical issues) PRACTICAL FEATURES: 1. Timestamps: when each log occurred 2. Data attachment: pass additional context 3. Icons: visual differentiation in console 4. Storage: keeps log history 5. Filtering: retrieve specific level logs 6. Summary: statistics on logs USE CASES: - Debug mode: detailed logging during development - Production: only warn/error logged - Error tracking: centralize error logging - Audit trail: keep history of app events --- PRODUCTION CONSIDERATIONS: In real apps, you might: - Write logs to file (fs module) - Send to remote logging service (Sentry, DataDog) - Implement log rotation (keep only recent logs) - Add performance metrics - Use structured logging (JSON format) This singleton pattern is perfect for such features since there's only one logger across the entire app.