type LogLevel = "INFO" | "WARN" | "ERROR" | "DEBUG"; type LogDomain = "server" | "upload" | "download" | "security" | "cleanup" | "request"; interface LogEntry { timestamp: string; level: LogLevel; domain: LogDomain; message: string; meta?: Record; } class Logger { private logDir = "./logs"; private currentDate = ""; private logBuffer: string[] = []; private flushInterval: number; constructor() { this.ensureLogDir(); // Flush logs every 5 seconds this.flushInterval = setInterval(() => this.flush(), 5000); } private async ensureLogDir() { try { await Deno.mkdir(this.logDir, { recursive: true }); } catch (error) { if (!(error instanceof Deno.errors.AlreadyExists)) { console.error("Failed to create log directory:", error); } } } private getDateString(): string { const now = new Date(); return now.toISOString().split('T')[0]; // YYYY-MM-DD } private formatLogEntry(entry: LogEntry): string { const metaStr = entry.meta ? ` ${JSON.stringify(entry.meta)}` : ""; return `[${entry.timestamp}] [${entry.level}] [${entry.domain}] ${entry.message}${metaStr}\n`; } private async writeToFile(domain: LogDomain, content: string) { const date = this.getDateString(); const filename = `${this.logDir}/${domain}-${date}.log`; try { await Deno.writeTextFile(filename, content, { append: true }); } catch (error) { console.error(`Failed to write to log file ${filename}:`, error); } } private log(level: LogLevel, domain: LogDomain, message: string, meta?: Record) { const timestamp = new Date().toISOString(); const entry: LogEntry = { timestamp, level, domain, message, meta }; // Write to console const formatted = this.formatLogEntry(entry); console.log(formatted.trim()); // Buffer for file writing this.logBuffer.push(JSON.stringify({ ...entry, file: domain })); } private async flush() { if (this.logBuffer.length === 0) return; const entries = [...this.logBuffer]; this.logBuffer = []; // Group entries by domain const byDomain = new Map(); for (const entryStr of entries) { try { const entry = JSON.parse(entryStr); const domain = entry.file as LogDomain; if (!byDomain.has(domain)) { byDomain.set(domain, []); } byDomain.get(domain)!.push(entry); } catch (error) { console.error("Failed to parse log entry:", error); } } // Write to files for (const [domain, domainEntries] of byDomain) { const content = domainEntries.map(e => this.formatLogEntry(e)).join(''); await this.writeToFile(domain, content); } } // Server domain logs server(level: LogLevel, message: string, meta?: Record) { this.log(level, "server", message, meta); } // Upload domain logs upload(level: LogLevel, message: string, meta?: Record) { this.log(level, "upload", message, meta); } // Download domain logs download(level: LogLevel, message: string, meta?: Record) { this.log(level, "download", message, meta); } // Security domain logs (rate limiting, IP tracking) security(level: LogLevel, message: string, meta?: Record) { this.log(level, "security", message, meta); } // Cleanup domain logs cleanup(level: LogLevel, message: string, meta?: Record) { this.log(level, "cleanup", message, meta); } // Request domain logs (HTTP requests) request(level: LogLevel, message: string, meta?: Record) { this.log(level, "request", message, meta); } // Legacy methods for backward compatibility info(message: string, meta?: Record) { this.log("INFO", "server", message, meta); } warn(message: string, meta?: Record) { this.log("WARN", "server", message, meta); } error(message: string, meta?: Record) { this.log("ERROR", "server", message, meta); } debug(message: string, meta?: Record) { this.log("DEBUG", "server", message, meta); } async shutdown() { clearInterval(this.flushInterval); await this.flush(); } } export const logger = new Logger(); // Ensure logs are flushed on exit addEventListener("unload", () => { logger.shutdown(); });