Files
crowd-funder-from-jason/src/utils/logger.ts
Matthew Raymer d30597a921 feat(logging): implement configurable log levels via VITE_LOG_LEVEL
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
2025-08-12 03:52:46 +00:00

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 };