const fs = require("fs") const path = require("path") class Logger { constructor(config = {}) { this.config = { level: config.level || "info", saveToFile: config.saveToFile || true, maxFileSize: config.maxFileSize || "10MB", maxFiles: config.maxFiles || 5, logDir: config.logDir || "./logs", } this.levels = { debug: 0, info: 1, warn: 2, error: 3, success: 1, } this.colors = { debug: "\x1b[36m", // Cyan info: "\x1b[34m", // Blue warn: "\x1b[33m", // Yellow error: "\x1b[31m", // Red success: "\x1b[32m", // Green reset: "\x1b[0m", } this.initLogDirectory() } initLogDirectory() { if (this.config.saveToFile && !fs.existsSync(this.config.logDir)) { fs.mkdirSync(this.config.logDir, { recursive: true }) } } formatMessage(level, message, data = null) { const timestamp = new Date().toISOString() const upperLevel = level.toUpperCase().padEnd(7) let logMessage = `[${timestamp}] [${upperLevel}] ${message}` if (data) { logMessage += ` ${JSON.stringify(data, null, 2)}` } return logMessage } shouldLog(level) { return this.levels[level] >= this.levels[this.config.level] } writeToFile(message) { if (!this.config.saveToFile) return const logFile = path.join(this.config.logDir, `openbrawl-${new Date().toISOString().split("T")[0]}.log`) try { fs.appendFileSync(logFile, message + "\n") this.rotateLogsIfNeeded(logFile) } catch (error) { console.error("Failed to write to log file:", error.message) } } rotateLogsIfNeeded(logFile) { try { const stats = fs.statSync(logFile) const maxSize = this.parseSize(this.config.maxFileSize) if (stats.size > maxSize) { const timestamp = new Date().toISOString().replace(/[:.]/g, "-") const rotatedFile = logFile.replace(".log", `-${timestamp}.log`) fs.renameSync(logFile, rotatedFile) this.cleanOldLogs() } } catch (error) { console.error("Failed to rotate logs:", error.message) } } cleanOldLogs() { try { const files = fs .readdirSync(this.config.logDir) .filter((file) => file.startsWith("openbrawl-") && file.endsWith(".log")) .map((file) => ({ name: file, path: path.join(this.config.logDir, file), time: fs.statSync(path.join(this.config.logDir, file)).mtime, })) .sort((a, b) => b.time - a.time) if (files.length > this.config.maxFiles) { files.slice(this.config.maxFiles).forEach((file) => { fs.unlinkSync(file.path) }) } } catch (error) { console.error("Failed to clean old logs:", error.message) } } parseSize(sizeStr) { const units = { B: 1, KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024 } const match = sizeStr.match(/^(\d+)(B|KB|MB|GB)$/i) if (!match) return 10 * 1024 * 1024 // Default 10MB return Number.parseInt(match[1]) * units[match[2].toUpperCase()] } log(level, message, data = null) { if (!this.shouldLog(level)) return const formattedMessage = this.formatMessage(level, message, data) const coloredMessage = `${this.colors[level]}${formattedMessage}${this.colors.reset}` console.log(coloredMessage) this.writeToFile(formattedMessage) } debug(message, data = null) { this.log("debug", message, data) } info(message, data = null) { this.log("info", message, data) } warn(message, data = null) { this.log("warn", message, data) } error(message, data = null) { this.log("error", message, data) } success(message, data = null) { this.log("success", message, data) } packet(direction, type, size, data = null) { const arrow = direction === "in" ? "📨" : "📤" const message = `${arrow} ${direction.toUpperCase()} | Type: ${type} | Size: ${size} bytes` this.debug(message, data) } connection(status, details = null) { const emoji = status === "connected" ? "✅" : status === "disconnected" ? "❌" : "🔄" this.info(`${emoji} Connection ${status}`, details) } } module.exports = Logger