// Centralized Logging Service // Provides structured logging with different levels and contexts // Replaces scattered console.log statements throughout the application import { getEnvVar, isProduction, isTest } from '../../utils/env'; export enum LogLevel { ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3, TRACE = 4, } export interface LogEntry { timestamp: string; level: LogLevel; message: string; context?: string; data?: unknown; error?: Error; } export interface LoggerConfig { level: LogLevel; enableConsole: boolean; enableStorage: boolean; maxStoredLogs: number; contexts: string[]; } class Logger { private config: LoggerConfig; private logs: LogEntry[] = []; constructor() { this.config = { level: this.getDefaultLogLevel(), enableConsole: true, enableStorage: !isProduction(), maxStoredLogs: 1000, contexts: [], }; } private getDefaultLogLevel(): LogLevel { if (isProduction()) { return LogLevel.WARN; } else if (isTest()) { return LogLevel.ERROR; } else { return LogLevel.DEBUG; } } private shouldLog(level: LogLevel, context?: string): boolean { // Check if level is enabled if (level > this.config.level) { return false; } // Check if context is filtered (if contexts filter is set) if (this.config.contexts.length > 0 && context) { return this.config.contexts.includes(context); } return true; } private formatMessage( level: LogLevel, message: string, context?: string ): string { const levelName = LogLevel[level]; const timestamp = new Date().toISOString(); const contextPart = context ? `[${context}] ` : ''; return `${timestamp} ${levelName} ${contextPart}${message}`; } private log( level: LogLevel, message: string, context?: string, data?: unknown, error?: Error ): void { if (!this.shouldLog(level, context)) { return; } const entry: LogEntry = { timestamp: new Date().toISOString(), level, message, context, data, error, }; // Store log entry if (this.config.enableStorage) { this.logs.push(entry); // Trim logs if exceeding max if (this.logs.length > this.config.maxStoredLogs) { this.logs = this.logs.slice(-this.config.maxStoredLogs); } } // Console output if (this.config.enableConsole) { const formattedMessage = this.formatMessage(level, message, context); switch (level) { case LogLevel.ERROR: if (error) { console.error(formattedMessage, data, error); } else { console.error(formattedMessage, data); } break; case LogLevel.WARN: console.warn(formattedMessage, data); break; case LogLevel.INFO: // eslint-disable-next-line no-console console.info(formattedMessage, data); break; case LogLevel.DEBUG: case LogLevel.TRACE: // eslint-disable-next-line no-console console.log(formattedMessage, data); break; } } } // Public logging methods error( message: string, context?: string, data?: unknown, error?: Error ): void { this.log(LogLevel.ERROR, message, context, data, error); } warn(message: string, context?: string, data?: unknown): void { this.log(LogLevel.WARN, message, context, data); } info(message: string, context?: string, data?: unknown): void { this.log(LogLevel.INFO, message, context, data); } debug(message: string, context?: string, data?: unknown): void { this.log(LogLevel.DEBUG, message, context, data); } trace(message: string, context?: string, data?: unknown): void { this.log(LogLevel.TRACE, message, context, data); } // Authentication specific logging auth = { login: (message: string, data?: unknown) => this.info(message, 'AUTH', data), logout: (message: string, data?: unknown) => this.info(message, 'AUTH', data), register: (message: string, data?: unknown) => this.info(message, 'AUTH', data), error: (message: string, error?: Error, data?: unknown) => this.error(message, 'AUTH', data, error), }; // Database specific logging db = { query: (message: string, data?: unknown) => this.debug(message, 'DATABASE', data), error: (message: string, error?: Error, data?: unknown) => this.error(message, 'DATABASE', data, error), warn: (message: string, data?: unknown) => this.warn(message, 'DATABASE', data), }; // API specific logging api = { request: (message: string, data?: unknown) => this.debug(message, 'API', data), response: (message: string, data?: unknown) => this.debug(message, 'API', data), error: (message: string, error?: Error, data?: unknown) => this.error(message, 'API', data, error), }; // UI specific logging ui = { action: (message: string, data?: unknown) => this.debug(message, 'UI', data), error: (message: string, error?: Error, data?: unknown) => this.error(message, 'UI', data, error), }; // Configuration methods setLevel(level: LogLevel): void { this.config.level = level; } setContext(contexts: string[]): void { this.config.contexts = contexts; } enableConsoleLogging(enable: boolean = true): void { this.config.enableConsole = enable; } enableStorageLogging(enable: boolean = true): void { this.config.enableStorage = enable; } // Utility methods getLogs(context?: string, level?: LogLevel): LogEntry[] { let filteredLogs = [...this.logs]; if (context) { filteredLogs = filteredLogs.filter(log => log.context === context); } if (level !== undefined) { filteredLogs = filteredLogs.filter(log => log.level === level); } return filteredLogs; } clearLogs(): void { this.logs = []; } exportLogs(): string { return JSON.stringify(this.logs, null, 2); } getLogSummary(): { [key: string]: number } { const summary: { [key: string]: number } = {}; this.logs.forEach(log => { const key = `${LogLevel[log.level]}${log.context ? `:${log.context}` : ''}`; summary[key] = (summary[key] || 0) + 1; }); return summary; } // Performance logging utilities time(label: string): void { if (this.shouldLog(LogLevel.DEBUG)) { // eslint-disable-next-line no-console console.time(label); } } timeEnd(label: string): void { if (this.shouldLog(LogLevel.DEBUG)) { // eslint-disable-next-line no-console console.timeEnd(label); } } // Group logging for related operations group(title: string, level: LogLevel = LogLevel.DEBUG): void { if (this.shouldLog(level) && this.config.enableConsole) { // eslint-disable-next-line no-console console.group(title); } } groupEnd(): void { if (this.config.enableConsole) { // eslint-disable-next-line no-console console.groupEnd(); } } // Development helpers table(data: unknown, context?: string): void { if (this.shouldLog(LogLevel.DEBUG, context) && this.config.enableConsole) { // eslint-disable-next-line no-console console.table(data); } } dir(object: unknown, context?: string): void { if (this.shouldLog(LogLevel.DEBUG, context) && this.config.enableConsole) { // eslint-disable-next-line no-console console.dir(object); } } } // Singleton logger instance export const logger = new Logger(); // Convenience exports for common logging patterns export const log = { error: logger.error.bind(logger), warn: logger.warn.bind(logger), info: logger.info.bind(logger), debug: logger.debug.bind(logger), trace: logger.trace.bind(logger), auth: logger.auth, db: logger.db, api: logger.api, ui: logger.ui, }; // Development helper to expose logger globally if (getEnvVar('DEBUG_MODE') === 'true' && typeof window !== 'undefined') { (window as unknown as { __logger: Logger }).__logger = logger; }