/** * Logging utilities - Structured logging and error handling */ const fsp = require('fs').promises; const path = require('path'); const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; /** * Create a structured logger */ function createLogger(LOG_LEVEL) { function log(level, context, message, data = {}) { if (LOG_LEVELS[level] < LOG_LEVEL) return; const entry = { t: new Date().toISOString(), level, ctx: context, msg: message, }; if (Object.keys(data).length) entry.data = data; const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.info; fn(JSON.stringify(entry)); } log.info = (ctx, msg, data) => log('info', ctx, msg, data); log.warn = (ctx, msg, data) => log('warn', ctx, msg, data); log.error = (ctx, msg, data) => log('error', ctx, msg, data); log.debug = (ctx, msg, data) => log('debug', ctx, msg, data); return log; } /** * Enhanced error logging with context tracking */ async function logError(ERROR_LOG_FILE, MAX_ERROR_LOG_SIZE, context, error, additionalInfo = {}, log) { const timestamp = new Date().toISOString(); // Extract request context const requestContext = {}; if (additionalInfo.req) { const req = additionalInfo.req; const clientIP = req.ip || req.socket?.remoteAddress || ''; requestContext.requestId = req.id; requestContext.ip = clientIP; requestContext.userAgent = req.get('user-agent'); requestContext.method = req.method; requestContext.path = req.path; delete additionalInfo.req; } const logEntry = { timestamp, context, ...requestContext, error: { message: error.message || error, stack: error.stack, code: error.code }, ...additionalInfo }; const contextInfo = Object.keys(requestContext).length > 0 ? `\nRequest Context: ${JSON.stringify(requestContext, null, 2)}` : ''; const logLine = `[${timestamp}] ${context}: ${error.message || error}\n${error.stack || ''}${contextInfo}\nAdditional Info: ${JSON.stringify(additionalInfo, null, 2)}\n${'='.repeat(80)}\n`; try { // Rotate log if it exceeds max size try { const stats = await fsp.stat(ERROR_LOG_FILE); if (stats.size > MAX_ERROR_LOG_SIZE) { const rotated = ERROR_LOG_FILE + '.1'; const exists = await fsp.access(rotated).then(() => true).catch(() => false); if (exists) await fsp.unlink(rotated); await fsp.rename(ERROR_LOG_FILE, rotated); } } catch (_) { /* file may not exist yet */ } await fsp.appendFile(ERROR_LOG_FILE, logLine); } catch (e) { if (log && log.error) { log.error('errorlog', 'Failed to write to error log', { error: e.message }); } } } /** * Return a safe error message without leaking internals */ function safeErrorMessage(error) { const msg = error.message || String(error); // Detect port conflict errors const portMatch = msg.match(/exposing port TCP [^:]*:(\d+)/); if (portMatch || msg.includes('port is already allocated') || msg.includes('ports are not available')) { const port = portMatch ? portMatch[1] : 'requested'; return `[DC-200] Port ${port} is already in use. Try a different port or stop the service using that port first.`; } // Only expose short, user-facing messages if (msg.length < 200 && !msg.includes('/') && !msg.includes('\\') && !msg.includes(' at ')) { return msg; } return 'An internal error occurred'; } module.exports = { LOG_LEVELS, createLogger, logError, safeErrorMessage, };