// Error Logger Utility // Centralized error logging with rotation and request context tracking const fsp = require('fs').promises; const path = require('path'); const { LIMITS } = require('./constants'); const ERROR_LOG_FILE = path.join(__dirname, 'error.log'); const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE; /** * Check if file exists */ async function exists(filepath) { try { await fsp.access(filepath); return true; } catch { return false; } } /** * Log error with context and rotation * @param {string} context - Where the error occurred * @param {Error|string} error - The error to log * @param {Object} additionalInfo - Additional context (req, etc.) */ async function logError(context, error, additionalInfo = {}) { const timestamp = new Date().toISOString(); // Extract request context if a request object is provided const requestContext = extractRequestContext(additionalInfo.req); if (additionalInfo.req) { delete additionalInfo.req; // Remove req to avoid circular refs } const logEntry = { timestamp, context, ...requestContext, error: { message: error.message || error, stack: error.stack, code: error.code }, ...additionalInfo }; // Format log line with request context 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 await rotateLogIfNeeded(); await fsp.appendFile(ERROR_LOG_FILE, logLine); } catch (e) { console.error('Failed to write to error log', e.message); } } /** * Extract request context from Express request object */ function extractRequestContext(req) { if (!req) return {}; const clientIP = req.ip || req.socket?.remoteAddress || ''; return { requestId: req.id, ip: clientIP, userAgent: req.get('user-agent'), method: req.method, path: req.path }; } /** * Rotate log file if it exceeds max size */ async function rotateLogIfNeeded() { try { const stats = await fsp.stat(ERROR_LOG_FILE); if (stats.size > MAX_ERROR_LOG_SIZE) { const rotated = ERROR_LOG_FILE + '.1'; if (await exists(rotated)) { await fsp.unlink(rotated); } await fsp.rename(ERROR_LOG_FILE, rotated); } } catch (_) { // File may not exist yet, that's fine } } /** * Return a safe error message to the client without leaking internals */ function safeErrorMessage(error) { const msg = error.message || String(error); // Detect port conflict errors from Docker 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 `Port ${port} is already in use. Please choose a different port or stop the conflicting service.`; } // Detect container not found errors if (msg.includes('No such container')) { return 'Container not found'; } // Detect network errors if (msg.includes('ECONNREFUSED') || msg.includes('ETIMEDOUT')) { return 'Service unavailable'; } // Generic safe message for unknown errors if (process.env.NODE_ENV === 'production') { return 'An error occurred. Please try again or contact support.'; } // In development, show the actual error return msg; } module.exports = { logError, safeErrorMessage };