const fs = require('fs'); const path = require('path'); // Singleton instance let loggerInstance = null; class ApiDebugLogger { constructor() { // Return existing instance if already created if (loggerInstance) { return loggerInstance; } // Use mounted volume directory that's accessible from host this.logFile = '/app/debug_logs/api_debug.log'; this.enabled = process.env.NODE_ENV === 'development' || process.env.API_DEBUG === 'true'; this.fileLoggingEnabled = false; this.initialized = false; // Debug logging setup (only run once) if (this.enabled && !this.initialized) { console.log(`🐛 ApiDebugLogger: Enabled (NODE_ENV=${process.env.NODE_ENV}, API_DEBUG=${process.env.API_DEBUG})`); console.log(`🐛 ApiDebugLogger: Log file path: ${this.logFile}`); // Ensure the debug_logs directory exists try { const debugDir = '/app/debug_logs'; if (!fs.existsSync(debugDir)) { fs.mkdirSync(debugDir, { recursive: true }); console.log(`🐛 ApiDebugLogger: Created debug logs directory at ${debugDir}`); } // Only create the file if it doesn't exist, otherwise just append if (!fs.existsSync(this.logFile)) { fs.writeFileSync(this.logFile, `# API Debug Log Started at ${new Date().toISOString()}\n`); console.log(`🐛 ApiDebugLogger: Created new log file`); } else { fs.appendFileSync(this.logFile, `\n# API Debug Log Session Started at ${new Date().toISOString()}\n`); console.log(`🐛 ApiDebugLogger: Appending to existing log file`); } this.fileLoggingEnabled = true; this.initialized = true; console.log(`🐛 ApiDebugLogger: File logging enabled at ${this.logFile} (mounted to host: ./debug_logs/)`); } catch (error) { console.warn(`⚠️ ApiDebugLogger: File logging disabled - using console only. Error: ${error.message}`); this.fileLoggingEnabled = false; } } else if (!this.enabled) { console.log(`🐛 ApiDebugLogger: Disabled (NODE_ENV=${process.env.NODE_ENV}, API_DEBUG=${process.env.API_DEBUG})`); } // Store the singleton instance loggerInstance = this; } log(method, url, statusCode, requestBody = {}, responseBody = {}, headers = {}) { if (!this.enabled) return; const timestamp = new Date().toISOString(); const sanitizedRequest = this.sanitizeData(requestBody); const sanitizedResponse = this.sanitizeData(responseBody); const sanitizedHeaders = this.sanitizeHeaders(headers); const logEntry = [ `[${timestamp}]`, method.toUpperCase(), url, `-`, `STATUS:${statusCode}`, `-`, `REQ:${JSON.stringify(sanitizedRequest)}`, `-`, `RES:${JSON.stringify(sanitizedResponse)}`, `-`, `HEADERS:${JSON.stringify(sanitizedHeaders)}` ].join(' '); // Always log to console console.log(`🐛 API Log: ${method.toUpperCase()} ${url} - ${statusCode}`); // Try to log to file if enabled if (this.fileLoggingEnabled) { try { fs.appendFileSync(this.logFile, logEntry + '\n'); } catch (error) { console.warn(`⚠️ File logging failed, disabling: ${error.message}`); this.fileLoggingEnabled = false; } } } sanitizeData(data) { // For debugging, return data as-is without redaction return data; } sanitizeHeaders(headers) { // For debugging, return headers as-is without redaction return headers; } logRequest(req) { if (!this.enabled) return; const timestamp = new Date().toISOString(); const fullUrl = req.originalUrl || req.url; const logEntry = `[${timestamp}] INCOMING ${req.method} ${fullUrl} - BODY:${JSON.stringify(this.sanitizeData(req.body))} - HEADERS:${JSON.stringify(this.sanitizeHeaders(req.headers))}`; // Always log to console console.log(`🐛 API Request: ${req.method} ${fullUrl}`); // Try to log to file if enabled if (this.fileLoggingEnabled) { try { fs.appendFileSync(this.logFile, logEntry + '\n'); } catch (error) { console.warn(`⚠️ File logging failed, disabling: ${error.message}`); this.fileLoggingEnabled = false; } } } logResponse(req, res, responseBody) { if (!this.enabled) return; const fullUrl = req.originalUrl || req.url; this.log( req.method, fullUrl, res.statusCode, req.body, responseBody, { 'content-type': res.getHeader('content-type'), 'user-agent': req.headers['user-agent'] } ); } clear() { try { fs.writeFileSync(this.logFile, '# API Debug Log Cleared at ' + new Date().toISOString() + '\n'); console.log('API debug log cleared'); } catch (error) { console.error('Failed to clear API debug log:', error); } } } // Middleware function for Express function apiDebugMiddleware(req, res, next) { const logger = new ApiDebugLogger(); // Log incoming request logger.logRequest(req); // Override res.json to capture response const originalJson = res.json; res.json = function(data) { logger.logResponse(req, res, data); return originalJson.call(this, data); }; next(); } module.exports = { ApiDebugLogger, apiDebugMiddleware };