/** * shared/logger.ts — Clone Architect * Logger léger avec niveaux + contexte + JSON mode pour CI */ type LogLevel = 'debug' | 'info' | 'warn' | 'error'; const LEVEL_WEIGHT: Record = { debug: 0, info: 1, warn: 2, error: 3, }; const CURRENT_LEVEL: LogLevel = (process.env.CLONE_LOG_LEVEL as LogLevel) || 'info'; const JSON_MODE = process.env.CLONE_LOG_JSON === '1'; function shouldLog(level: LogLevel): boolean { return LEVEL_WEIGHT[level] >= LEVEL_WEIGHT[CURRENT_LEVEL]; } function format(level: LogLevel, module: string, msg: string, meta?: any): string { if (JSON_MODE) { return JSON.stringify({ level, module, msg, meta, ts: new Date().toISOString() }); } const prefix = level === 'error' ? '❌' : level === 'warn' ? '⚠️ ' : level === 'debug' ? '🔍' : 'ℹ️ '; const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''; return `${prefix} [${module}] ${msg}${metaStr}`; } export class Logger { constructor(private module: string) {} debug(msg: string, meta?: any): void { if (shouldLog('debug')) console.log(format('debug', this.module, msg, meta)); } info(msg: string, meta?: any): void { if (shouldLog('info')) console.log(format('info', this.module, msg, meta)); } warn(msg: string, meta?: any): void { if (shouldLog('warn')) console.warn(format('warn', this.module, msg, meta)); } error(msg: string, meta?: any): void { if (shouldLog('error')) console.error(format('error', this.module, msg, meta)); } /** Wrap a promise with try/catch + warn on failure. */ async tryOrWarn(label: string, fn: () => Promise, fallback: T): Promise { try { return await fn(); } catch (err) { this.warn(`${label} failed`, { error: (err as Error).message }); return fallback; } } } export function getLogger(module: string): Logger { return new Logger(module); }