log-manager.js•4.21 kB
import fs from 'fs';
import path from 'path';
export class LogManager {
constructor(config) {
this.config = config;
this.logBuffer = [];
this.maxBufferSize = config.maxLogBufferSize || 1000;
this.logDir = config.logDir || './logs';
this.ensureLogDirectory();
}
ensureLogDirectory() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
addLogEntry(source, message, level = 'INFO') {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level: this.inferLogLevel(message, level),
source,
message: message.trim(),
thread: this.extractThread(message)
};
this.logBuffer.push(logEntry);
if (this.logBuffer.length > this.maxBufferSize) {
this.logBuffer.shift();
}
this.writeToFile(logEntry);
return logEntry;
}
inferLogLevel(message, defaultLevel = 'INFO') {
const upperMessage = message.toUpperCase();
if (upperMessage.includes('ERROR') || upperMessage.includes('EXCEPTION') || upperMessage.includes('FAILED')) {
return 'ERROR';
} else if (upperMessage.includes('WARN')) {
return 'WARN';
} else if (upperMessage.includes('DEBUG')) {
return 'DEBUG';
} else if (upperMessage.includes('INFO') || upperMessage.includes('STARTED') || upperMessage.includes('COMPLETED')) {
return 'INFO';
}
return defaultLevel;
}
extractThread(message) {
const threadMatch = message.match(/\[([^\]]+)\]/);
return threadMatch ? threadMatch[1] : null;
}
writeToFile(logEntry) {
try {
const logFile = path.join(this.logDir, `tomcat-${new Date().toISOString().split('T')[0]}.log`);
const logLine = `${logEntry.timestamp} [${logEntry.level}] ${logEntry.source}: ${logEntry.message}\n`;
fs.appendFileSync(logFile, logLine);
} catch (error) {
console.error('Failed to write log to file:', error);
}
}
getLogs(options = {}) {
const {
lines = 100,
level = null,
since = null,
source = null
} = options;
let filteredLogs = [...this.logBuffer];
if (level) {
const levelPriority = { 'DEBUG': 0, 'INFO': 1, 'WARN': 2, 'ERROR': 3 };
const minPriority = levelPriority[level.toUpperCase()] || 0;
filteredLogs = filteredLogs.filter(log =>
(levelPriority[log.level] || 0) >= minPriority
);
}
if (since) {
const sinceDate = new Date(since);
filteredLogs = filteredLogs.filter(log =>
new Date(log.timestamp) >= sinceDate
);
}
if (source) {
filteredLogs = filteredLogs.filter(log =>
log.source === source
);
}
return filteredLogs.slice(-lines);
}
getRecentLogs(count = 50) {
return this.logBuffer.slice(-count);
}
clearLogs() {
this.logBuffer = [];
try {
const files = fs.readdirSync(this.logDir);
for (const file of files) {
if (file.endsWith('.log')) {
fs.unlinkSync(path.join(this.logDir, file));
}
}
return { success: true, message: 'Logs cleared successfully' };
} catch (error) {
return { success: false, message: `Failed to clear log files: ${error.message}` };
}
}
getLogStats() {
const stats = {
bufferSize: this.logBuffer.length,
maxBufferSize: this.maxBufferSize,
levelCounts: { DEBUG: 0, INFO: 0, WARN: 0, ERROR: 0 },
sourceCounts: { stdout: 0, stderr: 0 }
};
for (const log of this.logBuffer) {
stats.levelCounts[log.level] = (stats.levelCounts[log.level] || 0) + 1;
stats.sourceCounts[log.source] = (stats.sourceCounts[log.source] || 0) + 1;
}
return stats;
}
tailLogs(callback, options = {}) {
const { lines = 10 } = options;
const initialLogs = this.getRecentLogs(lines);
callback(initialLogs);
const originalAddLogEntry = this.addLogEntry.bind(this);
this.addLogEntry = (source, message, level) => {
const logEntry = originalAddLogEntry(source, message, level);
callback([logEntry]);
return logEntry;
};
return () => {
this.addLogEntry = originalAddLogEntry;
};
}
}