UNPKG

@nodedaemon/core

Version:

Production-ready Node.js process manager with zero external dependencies

268 lines 9.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogManager = void 0; const fs_1 = require("fs"); const path_1 = require("path"); const zlib_1 = require("zlib"); const events_1 = require("events"); const helpers_1 = require("../utils/helpers"); const constants_1 = require("../utils/constants"); class LogManager extends events_1.EventEmitter { logStreams = new Map(); logBuffer = []; bufferIndex = 0; isShuttingDown = false; constructor() { super(); (0, helpers_1.ensureDir)(constants_1.LOG_DIR); this.setupMainLogStream(); } setupMainLogStream() { const mainLogPath = (0, path_1.join)(constants_1.LOG_DIR, 'daemon.log'); this.createLogStream('daemon', mainLogPath); } createLogStream(name, filePath) { if (this.logStreams.has(name)) { this.logStreams.get(name)?.end(); } const stream = (0, fs_1.createWriteStream)(filePath, { flags: 'a' }); stream.on('error', (error) => { console.error(`Log stream error for ${name}:`, error); }); this.logStreams.set(name, stream); return stream; } log(entry) { if (this.isShuttingDown) return; this.addToBuffer(entry); // Emit log event for WebUI this.emit('log', entry); const logLine = this.formatLogEntry(entry); const streamName = entry.processId || 'daemon'; let stream = this.logStreams.get(streamName); if (!stream) { const logPath = (0, path_1.join)(constants_1.LOG_DIR, `${streamName}.log`); stream = this.createLogStream(streamName, logPath); } stream.write(logLine + '\n', (error) => { if (error) { console.error(`Failed to write log for ${streamName}:`, error); } }); this.checkLogRotation(streamName); } info(message, data, processId) { this.log({ timestamp: Date.now(), level: 'info', processId, message, data }); } warn(message, data, processId) { this.log({ timestamp: Date.now(), level: 'warn', processId, message, data }); } error(message, data, processId) { this.log({ timestamp: Date.now(), level: 'error', processId, message, data }); } debug(message, data, processId) { this.log({ timestamp: Date.now(), level: 'debug', processId, message, data }); } addToBuffer(entry) { if (this.logBuffer.length < constants_1.LOG_BUFFER_SIZE) { this.logBuffer.push(entry); } else { this.logBuffer[this.bufferIndex] = entry; this.bufferIndex = (this.bufferIndex + 1) % constants_1.LOG_BUFFER_SIZE; } } getRecentLogs(count = 100, processId) { let logs = [...this.logBuffer]; if (processId) { logs = logs.filter(log => log.processId === processId); } return logs .sort((a, b) => b.timestamp - a.timestamp) .slice(0, count); } formatLogEntry(entry) { const timestamp = new Date(entry.timestamp).toISOString(); const level = entry.level.toUpperCase().padEnd(5); const processInfo = entry.processId ? `[${entry.processId}] ` : ''; const dataInfo = entry.data ? ` ${JSON.stringify(entry.data)}` : ''; return `${timestamp} ${level} ${processInfo}${entry.message}${dataInfo}`; } checkLogRotation(streamName) { const logPath = (0, path_1.join)(constants_1.LOG_DIR, `${streamName}.log`); if (!(0, fs_1.existsSync)(logPath)) return; try { const stats = (0, fs_1.statSync)(logPath); if (stats.size > constants_1.MAX_LOG_SIZE) { this.rotateLog(streamName, logPath); } } catch (error) { console.error(`Failed to check log size for ${streamName}:`, error); } } rotateLog(streamName, currentLogPath) { try { const stream = this.logStreams.get(streamName); if (stream) { stream.end(); this.logStreams.delete(streamName); } this.rotateLogFiles(currentLogPath); this.createLogStream(streamName, currentLogPath); this.info(`Log rotated for ${streamName}`); } catch (error) { console.error(`Failed to rotate log for ${streamName}:`, error); } } rotateLogFiles(logPath) { const basePath = logPath.replace('.log', ''); for (let i = constants_1.MAX_LOG_FILES - 1; i > 0; i--) { const oldPath = i === 1 ? logPath : `${basePath}.${i}.log.gz`; const newPath = `${basePath}.${i + 1}.log.gz`; if ((0, fs_1.existsSync)(oldPath)) { if (i === constants_1.MAX_LOG_FILES - 1) { (0, fs_1.unlinkSync)(oldPath); } else if (i === 1) { this.compressAndMove(oldPath, newPath); } else { (0, fs_1.renameSync)(oldPath, newPath); } } } if ((0, fs_1.existsSync)(logPath)) { this.compressAndMove(logPath, `${basePath}.1.log.gz`); } } compressAndMove(sourcePath, targetPath) { try { const gzip = (0, zlib_1.createGzip)(); const source = require('fs').createReadStream(sourcePath); const target = (0, fs_1.createWriteStream)(targetPath); source.pipe(gzip).pipe(target); target.on('finish', () => { try { (0, fs_1.unlinkSync)(sourcePath); } catch (error) { console.error(`Failed to remove source log file ${sourcePath}:`, error); } }); target.on('error', (error) => { console.error(`Failed to compress log file ${sourcePath}:`, error); }); } catch (error) { console.error(`Failed to compress and move log file:`, error); } } cleanup() { const logDir = constants_1.LOG_DIR; if (!(0, fs_1.existsSync)(logDir)) return; try { const files = (0, fs_1.readdirSync)(logDir); const logFiles = files.filter(file => file.endsWith('.log.gz')); logFiles.forEach(file => { const filePath = (0, path_1.join)(logDir, file); const match = file.match(/\.(\d+)\.log\.gz$/); if (match) { const index = parseInt(match[1], 10); if (index > constants_1.MAX_LOG_FILES) { try { (0, fs_1.unlinkSync)(filePath); this.debug(`Cleaned up old log file: ${file}`); } catch (error) { console.error(`Failed to cleanup log file ${file}:`, error); } } } }); } catch (error) { console.error('Failed to cleanup old log files:', error); } } async shutdown() { this.isShuttingDown = true; const closePromises = Array.from(this.logStreams.values()).map(stream => { return new Promise((resolve) => { if (stream.writable) { stream.end(() => resolve()); } else { resolve(); } }); }); await Promise.all(closePromises); this.logStreams.clear(); } getTotalLogSize() { if (!(0, fs_1.existsSync)(constants_1.LOG_DIR)) return 0; try { const files = (0, fs_1.readdirSync)(constants_1.LOG_DIR); return files.reduce((total, file) => { const filePath = (0, path_1.join)(constants_1.LOG_DIR, file); try { return total + (0, fs_1.statSync)(filePath).size; } catch { return total; } }, 0); } catch { return 0; } } getLogFiles() { if (!(0, fs_1.existsSync)(constants_1.LOG_DIR)) return []; try { return (0, fs_1.readdirSync)(constants_1.LOG_DIR) .filter(file => file.endsWith('.log') || file.endsWith('.log.gz')) .sort((a, b) => { const aTime = (0, fs_1.statSync)((0, path_1.join)(constants_1.LOG_DIR, a)).mtime.getTime(); const bTime = (0, fs_1.statSync)((0, path_1.join)(constants_1.LOG_DIR, b)).mtime.getTime(); return bTime - aTime; }); } catch { return []; } } } exports.LogManager = LogManager; //# sourceMappingURL=LogManager.js.map