/** * Enhanced logger with self-contained database logging * * Provides comprehensive logging with console and database output. * Supports configurable log levels via VITE_LOG_LEVEL environment variable. * * @author Matthew Raymer * @version 2.1.0 * @since 2025-01-25 */ import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; const _memoryLogs: string[] = []; export function appendToMemoryLogs(message: string): void { _memoryLogs.push(`${new Date().toISOString()}: ${message}`); if (_memoryLogs.length > 1000) { _memoryLogs.splice(0, _memoryLogs.length - 1000); } } export function getMemoryLogs(): string[] { return [..._memoryLogs]; } export function safeStringify(obj: unknown) { const seen = new WeakSet(); return JSON.stringify(obj, (_key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; } seen.add(value); } if (typeof value === "function") { return `[Function: ${value.name || "anonymous"}]`; } return value; }); } // Determine if we should suppress verbose logging (for Electron) const isElectron = process.env.VITE_PLATFORM === "electron"; const isProduction = process.env.NODE_ENV === "production"; // Log level configuration via environment variable const LOG_LEVELS = { error: 0, warn: 1, info: 2, debug: 3, } as const; type LogLevel = keyof typeof LOG_LEVELS; // Parse VITE_LOG_LEVEL environment variable const getLogLevel = (): LogLevel => { const envLogLevel = process.env.VITE_LOG_LEVEL?.toLowerCase(); if (envLogLevel && envLogLevel in LOG_LEVELS) { return envLogLevel as LogLevel; } // Default log levels based on environment if (isProduction && !isElectron) { return "warn"; // Production web: warnings and errors only } else if (isElectron) { return "error"; // Electron: errors only } else { return "info"; // Development/Capacitor: info and above } }; const currentLogLevel = getLogLevel(); const currentLevelValue = LOG_LEVELS[currentLogLevel]; // Helper function to check if a log level should be displayed const shouldLog = (level: LogLevel): boolean => { return LOG_LEVELS[level] <= currentLevelValue; }; // Track initialization state to prevent circular dependencies let isInitializing = true; // Mark initialization as complete after a delay to allow all services to start setTimeout(() => { isInitializing = false; }, 2000); // 2 second delay to allow all services to initialize // Function to check if we should skip database logging during initialization function shouldSkipDatabaseLogging(message: string): boolean { // Skip during initialization phase if (isInitializing) { return true; } // Skip specific initialization-related messages even after initialization const initializationMessages = [ "[PlatformServiceFactory]", "[SQLWorker]", "[WebPlatformService]", "[CapacitorPlatformService]", "[CapacitorMigration]", "[DB-Integrity]", "[Migration]", "[IndexedDBMigrationService]", "Creating singleton instance", "Worker loaded", "Worker initialized", "Platform service", ]; return initializationMessages.some((pattern) => message.includes(pattern)); } // Self-contained database logging function async function logToDatabase( message: string, level: string = "info", ): Promise { // Prevent infinite logging loops if (isInitializing || shouldSkipDatabaseLogging(message)) { return; } try { const platform = PlatformServiceFactory.getInstance(); const todayKey = new Date().toDateString(); await platform.dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [ todayKey, `[${level.toUpperCase()}] ${message}`, ]); } catch (error) { // Fallback to console if database logging fails // eslint-disable-next-line no-console console.error(`[Logger] Database logging failed: ${error}`); } } // Enhanced logger with self-contained database methods and log level control export const logger = { debug: (message: string, ...args: unknown[]) => { // Debug logs only show if VITE_LOG_LEVEL allows it if (shouldLog("debug")) { // eslint-disable-next-line no-console console.debug(message, ...args); } // Don't log debug messages to database to reduce noise }, log: (message: string, ...args: unknown[]) => { // Regular logs - show if VITE_LOG_LEVEL allows info level if (shouldLog("info")) { // eslint-disable-next-line no-console console.log(message, ...args); } // Database logging const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; logToDatabase(message + argsString, "info"); }, info: (message: string, ...args: unknown[]) => { if (shouldLog("info")) { // eslint-disable-next-line no-console console.info(message, ...args); } // Database logging const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; logToDatabase(message + argsString, "info"); }, warn: (message: string, ...args: unknown[]) => { if (shouldLog("warn")) { // eslint-disable-next-line no-console console.warn(message, ...args); } // Database logging const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; logToDatabase(message + argsString, "warn"); }, error: (message: string, ...args: unknown[]) => { if (shouldLog("error")) { // eslint-disable-next-line no-console console.error(message, ...args); } // Database logging const messageString = safeStringify(message); const argsString = args.length > 0 ? safeStringify(args) : ""; logToDatabase(messageString + argsString, "error"); }, // New database-focused methods (self-contained) toDb: async (message: string, level?: string): Promise => { await logToDatabase(message, level || "info"); }, toConsoleAndDb: async (message: string, isError = false): Promise => { // Console output based on log level if (isError && shouldLog("error")) { // eslint-disable-next-line no-console console.error(message); } else if (!isError && shouldLog("info")) { // eslint-disable-next-line no-console console.log(message); } // Database output await logToDatabase(message, isError ? "error" : "info"); }, // Component context methods withContext: (componentName?: string) => ({ log: (message: string, level?: string) => logToDatabase(`[${componentName}] ${message}`, level), error: (message: string) => logToDatabase(`[${componentName}] ${message}`, "error"), }), // Log level information methods getCurrentLevel: (): LogLevel => currentLogLevel, getCurrentLevelValue: (): number => currentLevelValue, isLevelEnabled: (level: LogLevel): boolean => shouldLog(level), getAvailableLevels: (): LogLevel[] => Object.keys(LOG_LEVELS) as LogLevel[], }; // Function to manually mark initialization as complete export function markInitializationComplete(): void { isInitializing = false; } // Function to check initialization status export function isInitializationPhase(): boolean { return isInitializing; } // Add CommonJS export for Electron if (typeof module !== "undefined" && module.exports) { module.exports = { logger, markInitializationComplete, isInitializationPhase, }; } // Add default export for ESM export default { logger, markInitializationComplete, isInitializationPhase };