forked from jsnbuchanan/crowd-funder-for-time-pwa
Add comprehensive logging configuration system with environment variable support. Environment files now include appropriate log levels per build mode: - Development: debug (maximum visibility) - Production: warn (minimal noise) - Testing: info (balanced output) Includes smart default behavior based on platform and environment, enhanced logger methods for level checking, and comprehensive documentation. All existing logging calls remain backward compatible. Closes logging configuration request
256 lines
7.4 KiB
TypeScript
256 lines
7.4 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
// 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<void> => {
|
|
await logToDatabase(message, level || "info");
|
|
},
|
|
|
|
toConsoleAndDb: async (message: string, isError = false): Promise<void> => {
|
|
// 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 };
|