/** * Request Logger Middleware * Logs all incoming requests with timing information */ // ANSI color codes for terminal output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', } /** * Get color based on status code */ function getStatusColor(status) { if (status >= 500) return colors.red if (status >= 400) return colors.yellow if (status >= 300) return colors.cyan if (status >= 200) return colors.green return colors.reset } /** * Get color based on HTTP method */ function getMethodColor(method) { const methodColors = { GET: colors.green, POST: colors.blue, PUT: colors.yellow, PATCH: colors.yellow, DELETE: colors.red, } return methodColors[method] || colors.reset } /** * Format duration for display */ function formatDuration(ms) { if (ms < 1) return `${(ms * 1000).toFixed(0)}µs` if (ms < 1000) return `${ms.toFixed(0)}ms` return `${(ms / 1000).toFixed(2)}s` } /** * Logger middleware */ export function logger(options = {}) { const { skip = () => false, format = 'dev', } = options return (req, res, next) => { if (skip(req, res)) { return next() } const startTime = process.hrtime.bigint() const timestamp = new Date().toISOString() // Capture response const originalSend = res.send res.send = function (body) { const endTime = process.hrtime.bigint() const duration = Number(endTime - startTime) / 1e6 // Convert to ms const statusColor = getStatusColor(res.statusCode) const methodColor = getMethodColor(req.method) // Log format const logLine = [ `${colors.dim}[${timestamp}]${colors.reset}`, `${methodColor}${req.method.padEnd(7)}${colors.reset}`, `${req.originalUrl}`, `${statusColor}${res.statusCode}${colors.reset}`, `${colors.dim}${formatDuration(duration)}${colors.reset}`, ].join(' ') console.log(logLine) // Log errors in detail if (res.statusCode >= 400 && body) { try { const parsed = typeof body === 'string' ? JSON.parse(body) : body if (parsed.error) { console.log(` ${colors.red}→ ${parsed.error.message}${colors.reset}`) } } catch (e) { // Body is not JSON } } return originalSend.call(this, body) } next() } } /** * Log levels */ export const log = { info: (message, data = {}) => { console.log(`${colors.blue}[INFO]${colors.reset} ${message}`, Object.keys(data).length ? data : '') }, warn: (message, data = {}) => { console.log(`${colors.yellow}[WARN]${colors.reset} ${message}`, Object.keys(data).length ? data : '') }, error: (message, data = {}) => { console.error(`${colors.red}[ERROR]${colors.reset} ${message}`, Object.keys(data).length ? data : '') }, debug: (message, data = {}) => { if (process.env.NODE_ENV === 'development') { console.log(`${colors.magenta}[DEBUG]${colors.reset} ${message}`, Object.keys(data).length ? data : '') } }, success: (message, data = {}) => { console.log(`${colors.green}[OK]${colors.reset} ${message}`, Object.keys(data).length ? data : '') }, }