Spaces:
Runtime error
Runtime error
| /** | |
| * 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<LogLevel, { icon: string; color: string; bg: string; label: string }> = { | |
| 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<string, string> = { | |
| 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<string, string> = { | |
| 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}`); | |
| } | |