const fs = require('fs'); const path = require('path'); class CsvLogger { /** * @param {string} logDir - Directory where log files will be saved * @param {string[]} headers - CSV header row * @param {number} maxFileAgeDays - How many days log files should be kept */ constructor(logDir, headers = ['timestamp', 'level', 'message'], maxFileAgeDays = 30) { this.logDir = logDir; this.headers = headers; this.maxFileAgeDays = maxFileAgeDays; this.timestamp = null if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } // Cleanup old log files once on startup this.deleteOldLogFiles().catch(console.error); } /** * Writes a new log entry to today's CSV file * @param {string} level - Log level (e.g., 'info', 'error') * @param {string} message - Log message * @param {string[]} extraFields - Optional additional fields */ log(level, message, extraFields = []) { const timestamp = new Date().toISOString(); const filePath = this.getLogFilePath(); this.ensureFileWithHeaders(filePath); const row = [timestamp, level, message, ...extraFields] .map(this.escapeCsv) .join(',') + '\n'; fs.appendFileSync(filePath, row, (err) => { if (err) { console.error('Failed to write to log file:', err); } }); } /** * Constructs the log file path for today */ getLogFilePath() { const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD let filename = "logs.csv" if (this.timestamp == null) { filename = `logs_${today}.csv` } else { filename = `${this.timestamp}_logs.csv` } return path.join(this.logDir, filename); } /** * Ensures headers are written if file doesn't exist */ ensureFileWithHeaders(filePath) { if (!fs.existsSync(filePath)) { const headerRow = this.headers.join(',') + '\n'; fs.writeFileSync(filePath, headerRow); } } /** * Escapes a value for safe CSV output */ escapeCsv(value) { if (value == null) return ''; const str = String(value); return /[",\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str; } /** * Deletes log files older than maxFileAgeDays */ async deleteOldLogFiles() { const files = await fs.promises.readdir(this.logDir); const now = Date.now(); const maxAgeMs = this.maxFileAgeDays * 24 * 60 * 60 * 1000; for (const file of files) { const filePath = path.join(this.logDir, file); if (!/^logs_\d{4}-\d{2}-\d{2}\.csv$/.test(file)) continue; try { const stats = await fs.promises.stat(filePath); const age = now - stats.mtimeMs; if (age > maxAgeMs) { await fs.promises.unlink(filePath); console.log(`Deleted old log file: ${file}`); } } catch (err) { console.warn(`Could not process file ${file}: ${err.message}`); } } } } module.exports = CsvLogger;