@nodedaemon/core
Version:
Production-ready Node.js process manager with zero external dependencies
268 lines • 9.32 kB
JavaScript
;
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