// utils/logger.js import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, '..'); const LOG_DIR = path.join(projectRoot, 'logs'); // Ensure logs directory exists if (!fs.existsSync(LOG_DIR)) { fs.mkdirSync(LOG_DIR, { recursive: true }); } const getLogFileName = () => { const date = new Date().toISOString().split('T')[0]; return path.join(LOG_DIR, `${date}.log`); }; // Cleanup logs older than 14 days const cleanOldLogs = () => { try { const files = fs.readdirSync(LOG_DIR); const now = Date.now(); const fourteenDaysAgo = now - (14 * 24 * 60 * 60 * 1000); files.forEach(file => { if (!file.endsWith('.log')) return; const filePath = path.join(LOG_DIR, file); const stats = fs.statSync(filePath); if (stats.mtimeMs < fourteenDaysAgo) { fs.unlinkSync(filePath); } }); } catch (err) { console.error(`[LOGGER] Failed to clean old logs: ${err.message}`); } }; // Run cleanup on startup cleanOldLogs(); const levels = { info: 'INFO ', warn: 'WARN ', error: 'ERROR', debug: 'DEBUG' }; /** * Safe string conversion for any value */ function safeString(value) { if (value === null) return 'null'; if (value === undefined) return 'undefined'; if (typeof value === 'object') { try { return JSON.stringify(value, null, 2); } catch (e) { return `[Object ${Object.prototype.toString.call(value)}]`; } } return String(value); } export function logger(module, message, level = 'info') { const effectiveLevel = (process.env.LOG_LEVEL || 'info').toLowerCase(); const levelOrder = { debug: 10, info: 20, warn: 30, error: 40 }; if ((levelOrder[level] || 20) < (levelOrder[effectiveLevel] || 20)) { return; // filtered } const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const millis = String(now.getMilliseconds()).padStart(3, '0'); const timestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${millis}`; const levelStr = levels[level] || 'INFO '; // Ultra-safe message conversion let messageStr = ''; if (message == null) { messageStr = message === null ? 'null' : 'undefined'; } else if (typeof message === 'object') { try { messageStr = JSON.stringify(message, null, 2); } catch (e) { messageStr = `[Object]`; } } else { messageStr = String(message); } const logLine = `[${timestamp}] [${levelStr}] [${module}] ${messageStr}\n`; // Console output if (level === 'error') { console.error(logLine.trim()); } else if (level === 'warn') { console.warn(logLine.trim()); } else { console.log(logLine.trim()); } // Write to daily log file try { const logFile = getLogFileName(); fs.appendFileSync(logFile, logLine, 'utf8'); } catch (err) { console.error(`[LOGGER] Failed to write to log file: ${err.message}`); } } // Convenience methods export const logInfo = (module, msg) => logger(module, msg, 'info'); export const logWarn = (module, msg) => logger(module, msg, 'warn'); export const logError = (module, msg) => logger(module, msg, 'error'); export const logDebug = (module, msg) => logger(module, msg, 'debug'); export default logger;