type LogLevel = 'error' | 'warn' | 'info' | 'debug'; const LEVELS: Record = { error: 0, warn: 1, info: 2, debug: 3 }; const COLORS: Record = { error: '\x1b[31m', warn: '\x1b[33m', info: '\x1b[34m', debug: '\x1b[90m', }; const RESET = '\x1b[0m'; type LogFormat = 'text' | 'json'; interface LogSettings { level?: string; format?: string; } // The config manager pushes the resolved logLevel / logFormat (admin config, // then LOG_LEVEL / LOG_FORMAT, then default) here after every load and save. // Kept on globalThis so every bundle that loads this module sees the same // values, like the config manager singleton itself. const SETTINGS_KEY = Symbol.for('bulwark.logger.settings'); type GlobalWithLogSettings = typeof globalThis & { [SETTINGS_KEY]?: LogSettings }; const g = globalThis as GlobalWithLogSettings; /** Use these settings instead of LOG_LEVEL / LOG_FORMAT. */ export function configureLogger(settings: LogSettings): void { g[SETTINGS_KEY] = { ...settings }; } function getLevel(): number { const configured = (g[SETTINGS_KEY]?.level ?? process.env.LOG_LEVEL)?.toLowerCase() as LogLevel | undefined; return configured && configured in LEVELS ? LEVELS[configured] : LEVELS.info; } function isJson(): boolean { const configured = (g[SETTINGS_KEY]?.format ?? process.env.LOG_FORMAT)?.toLowerCase() as LogFormat | undefined; return configured === 'json'; } function log(level: LogLevel, message: string, extra?: Record): void { if (LEVELS[level] > getLevel()) return; if (isJson()) { const entry: Record = { timestamp: new Date().toISOString(), level, message, ...extra, }; const out = JSON.stringify(entry); if (level === 'error' || level === 'warn') { console.error(out); } else { console.log(out); } return; } const color = COLORS[level]; const tag = `${color}[${level.toUpperCase().padEnd(5)}]${RESET}`; const ts = new Date().toISOString(); const suffix = extra && Object.keys(extra).length > 0 ? ` ${COLORS.debug}${JSON.stringify(extra)}${RESET}` : ''; if (level === 'error' || level === 'warn') { console.error(`${tag} ${ts} ${message}${suffix}`); } else { console.log(`${tag} ${ts} ${message}${suffix}`); } } export const logger = { error: (message: string, extra?: Record) => log('error', message, extra), warn: (message: string, extra?: Record) => log('warn', message, extra), info: (message: string, extra?: Record) => log('info', message, extra), debug: (message: string, extra?: Record) => log('debug', message, extra), request: (method: string, path: string, status: number, durationMs: number) => log('info', `${method} ${path} ${status}`, { method, path, status, durationMs }), };