/** * Modern colored logger — structured output with ANSI styling. * Never log: passwords, tokens, keys, user content, or raw error objects. */ type LogLevel = 'info' | 'warn' | 'error' | 'debug'; const C = { reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', gray: '\x1b[90m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', bgYellow: '\x1b[43m', bgBlue: '\x1b[44m', bgMagenta: '\x1b[45m', bgCyan: '\x1b[46m', }; const LEVEL_STYLES: Record = { info: { icon: '●', color: C.green, bg: C.bgGreen, label: ' INFO ' }, warn: { icon: '▲', color: C.yellow, bg: C.bgYellow, label: ' WARN ' }, error: { icon: '✖', color: C.red, bg: C.bgRed, label: ' ERROR' }, debug: { icon: '◆', color: C.cyan, bg: C.bgCyan, label: ' DEBUG' }, }; function timestamp(): string { return new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function formatLevel(level: LogLevel): string { const s = LEVEL_STYLES[level]; return `${s.bg}${C.bold}${C.white} ${s.icon} ${s.label} ${C.reset}`; } function formatContext(ctx?: string): string { if (!ctx) return ''; return `${C.gray}[${C.cyan}${ctx}${C.gray}]${C.reset} `; } function formatMessage(msg: string): string { return `${C.white}${msg}${C.reset}`; } function log(level: LogLevel, message: string, context?: string): void { const ts = `${C.dim}${timestamp()}${C.reset}`; const lvl = formatLevel(level); const ctx = formatContext(context); const msg = formatMessage(message); const line = `${C.dim}┌─${C.reset} ${ts} ${lvl} ${ctx}${msg}`; if (level === 'error') { console.error(line); } else if (level === 'warn') { console.warn(line); } else { console.log(line); } } export function logInfo(message: string, context?: string) { log('info', message, context); } export function logWarn(message: string, context?: string) { log('warn', message, context); } export function logError(message: string, context?: string) { log('error', message, context); } export function logDebug(message: string, context?: string) { log('debug', message, context); } // ─── Request Logger Middleware ─────────────────────────────────────────────── export function requestLogger() { return (req: any, _res: any, next: () => void) => { const method = req.method; const url = req.originalUrl || req.url; const METHOD_COLORS: Record = { GET: C.green, POST: C.blue, PUT: C.yellow, DELETE: C.red, PATCH: C.magenta, OPTIONS: C.gray, }; const color = METHOD_COLORS[method] || C.white; const ts = `${C.dim}${timestamp()}${C.reset}`; const methodStr = `${color}${C.bold}${method.padEnd(7)}${C.reset}`; const urlStr = `${C.white}${url}${C.reset}`; const ip = req.ip || req.connection?.remoteAddress || ''; console.log(`${C.dim}┌─${C.reset} ${ts} ${C.bgBlue}${C.bold}${C.white} REQ ${C.reset} ${methodStr} ${urlStr} ${C.dim}${C.gray}← ${ip}${C.reset}`); next(); }; } // ─── Server Banner ────────────────────────────────────────────────────────── export function printBanner(port: string | number, env: string) { const isProd = env === 'production'; const envColor = isProd ? C.red : C.green; const envLabel = isProd ? 'PRODUCTION' : 'DEVELOPMENT'; const line = `${C.dim}────────────────────────────────────────────${C.reset}`; console.log(''); console.log(`${C.blue}${C.bold} ╔══════════════════════════════════════════╗${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.cyan}${C.bold}MASAR${C.reset} ${C.dim}·${C.reset} ${C.white}Backend API${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ╠══════════════════════════════════════════╣${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.gray}Status${C.reset} ${C.green}${C.bold}● RUNNING${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.gray}Port${C.reset} ${C.white}${C.bold}${port}${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.gray}Environment${C.reset} ${envColor}${C.bold}${envLabel}${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.gray}Time${C.reset} ${C.white}${new Date().toLocaleString('en-US', { timeZone: 'Asia/Baghdad' })}${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ╠══════════════════════════════════════════╣${C.reset}`); const routes: [string, string, string, string][] = [ ['GET', '/api/health', C.green, 'Health check'], ['POST', '/api/auth/*', C.blue, 'Authentication'], ['GET', '/api/users/*', C.green, 'User profiles'], ['GET', '/api/courses/*', C.green, 'Course content'], ['POST', '/api/challenges/*', C.blue, 'Challenge submit'], ['GET', '/api/leaderboard', C.green, 'Leaderboard'], ['GET', '/api/admin/*', C.green, 'Admin panel'], ['GET', '/api/teams/*', C.green, 'Team management'], ]; routes.forEach(([method, path, color, label]) => { const m = `${color}${C.bold}${method.padEnd(6)}${C.reset}`; const p = `${C.white}${path.padEnd(22)}${C.reset}`; const l = `${C.dim}${label}${C.reset}`; console.log(`${C.blue}${C.bold} ║${C.reset} ${m} ${p} ${l} ${C.blue}${C.bold}║${C.reset}`); }); console.log(`${C.blue}${C.bold} ║${C.reset} ${C.blue}${C.bold}║${C.reset}`); console.log(`${C.blue}${C.bold} ╚══════════════════════════════════════════╝${C.reset}`); console.log(''); console.log(`${C.dim} ${line}${C.reset}`); console.log(`${C.cyan} → Ready to accept connections${C.reset}`); console.log(`${C.dim} ${line}${C.reset}`); console.log(''); } // ─── Response Logger (call in middleware after res finishes) ───────────────── export function responseLogger() { return (req: any, res: any, next: () => void) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; const status = res.statusCode; const method = req.method; const url = req.originalUrl || req.url; let statusBg = C.bgGreen; if (status >= 500) { statusBg = C.bgRed; } else if (status >= 400) { statusBg = C.bgYellow; } else if (status >= 300) { statusBg = C.bgCyan; } const durationColor = duration > 1000 ? C.red : duration > 500 ? C.yellow : C.green; const METHOD_COLORS: Record = { GET: C.green, POST: C.blue, PUT: C.yellow, DELETE: C.red, PATCH: C.magenta, }; const mColor = METHOD_COLORS[method] || C.white; const ts = `${C.dim}${timestamp()}${C.reset}`; const m = `${mColor}${C.bold}${method.padEnd(7)}${C.reset}`; const s = `${statusBg}${C.bold}${C.white} ${status} ${C.reset}`; const d = `${durationColor}${C.bold}${duration}ms${C.reset}`; const u = `${C.white}${url}${C.reset}`; console.log(`${C.dim}└─${C.reset} ${ts} ${C.magenta}${C.bold} RES ${C.reset} ${m} ${s} ${u} ${C.dim}${C.gray}→${C.reset} ${d}`); }); next(); }; } // ─── Security Alert ───────────────────────────────────────────────────────── export function logSecurity(message: string, ip?: string) { const ts = `${C.dim}${timestamp()}${C.reset}`; const icon = `${C.bgRed}${C.bold}${C.white} ⚠ SECURITY ${C.reset}`; const ipStr = ip ? `${C.gray}← ${ip}${C.reset}` : ''; console.log(`${C.red}${C.bold} ┌─${C.reset} ${ts} ${icon} ${C.red}${C.bold}${message}${C.reset} ${ipStr}`); } // ─── Activity Log (console only) ──────────────────────────────────────────── export function logActivityConsole(action: string, details?: string) { const ts = `${C.dim}${timestamp()}${C.reset}`; const icon = `${C.bgMagenta}${C.bold}${C.white} ♦ LOG ${C.reset}`; const act = `${C.magenta}${C.bold}${action}${C.reset}`; const det = details ? `${C.dim}— ${details}${C.reset}` : ''; console.log(`${C.dim}┌─${C.reset} ${ts} ${icon} ${act} ${det}`); }