Initial commit: LogMaster with Viewer Collector Monitor modes
This commit is contained in:
280
dist-server/logSourceManager.js
Normal file
280
dist-server/logSourceManager.js
Normal file
@@ -0,0 +1,280 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.LogSourceManager = void 0;
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const zlib = __importStar(require("zlib"));
|
||||
const child_process_1 = require("child_process");
|
||||
class LogSourceManager {
|
||||
configManager;
|
||||
sources = new Map();
|
||||
constructor(configManager) {
|
||||
this.configManager = configManager;
|
||||
this.initializeSources();
|
||||
}
|
||||
initializeSources() {
|
||||
const configSources = this.configManager.getSources();
|
||||
for (const source of configSources) {
|
||||
this.addSource(source);
|
||||
}
|
||||
}
|
||||
addSource(sourcePath) {
|
||||
if (sourcePath.includes('*')) {
|
||||
const resolvedPaths = this.resolveGlob(sourcePath);
|
||||
for (const rp of resolvedPaths) {
|
||||
this.addSingleSource(rp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.addSingleSource(sourcePath);
|
||||
}
|
||||
this.configManager.updateSources(Array.from(this.sources.keys()));
|
||||
}
|
||||
addSingleSource(sourcePath) {
|
||||
const resolved = path.resolve(sourcePath);
|
||||
const type = this.detectType(resolved);
|
||||
let size = 0;
|
||||
let lastModified = null;
|
||||
let exists = false;
|
||||
try {
|
||||
const stats = fs.statSync(resolved);
|
||||
size = stats.size;
|
||||
lastModified = stats.mtime.toISOString();
|
||||
exists = true;
|
||||
}
|
||||
catch {
|
||||
// File doesn't exist yet
|
||||
}
|
||||
this.sources.set(resolved, {
|
||||
path: resolved,
|
||||
displayName: path.basename(resolved),
|
||||
type,
|
||||
size,
|
||||
lastModified,
|
||||
exists,
|
||||
});
|
||||
}
|
||||
removeSource(sourcePath) {
|
||||
const resolved = path.resolve(sourcePath);
|
||||
this.sources.delete(resolved);
|
||||
this.configManager.updateSources(Array.from(this.sources.keys()));
|
||||
}
|
||||
getSources() {
|
||||
for (const [key, source] of this.sources) {
|
||||
try {
|
||||
const stats = fs.statSync(key);
|
||||
source.size = stats.size;
|
||||
source.lastModified = stats.mtime.toISOString();
|
||||
source.exists = true;
|
||||
}
|
||||
catch {
|
||||
source.exists = false;
|
||||
}
|
||||
}
|
||||
return Array.from(this.sources.values());
|
||||
}
|
||||
detectType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
switch (ext) {
|
||||
case '.gz':
|
||||
case '.gzip': return 'gzip';
|
||||
case '.bz2':
|
||||
case '.bzip2': return 'bzip2';
|
||||
case '.xz': return 'xz';
|
||||
case '.zst':
|
||||
case '.zstd': return 'zstd';
|
||||
default: return 'text';
|
||||
}
|
||||
}
|
||||
async readLog(sourcePath) {
|
||||
const resolved = path.resolve(sourcePath);
|
||||
const source = this.sources.get(resolved);
|
||||
if (!source)
|
||||
throw new Error(`Source not found: ${sourcePath}`);
|
||||
const config = this.configManager.getConfig();
|
||||
const content = this.readFileContent(resolved, source.type);
|
||||
const lines = content.split('\n');
|
||||
const maxLines = config.maxLines;
|
||||
const startIndex = lines.length > maxLines ? lines.length - maxLines : 0;
|
||||
const entries = [];
|
||||
for (let i = startIndex; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.trim() === '')
|
||||
continue;
|
||||
entries.push({
|
||||
line,
|
||||
lineNumber: i + 1,
|
||||
source: resolved,
|
||||
timestamp: this.extractTimestamp(line, config.timestampRegex),
|
||||
level: this.extractLevel(line),
|
||||
});
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
async readAllLogs() {
|
||||
const allEntries = [];
|
||||
for (const [sourcePath] of this.sources) {
|
||||
try {
|
||||
const entries = await this.readLog(sourcePath);
|
||||
allEntries.push(...entries);
|
||||
}
|
||||
catch (err) {
|
||||
allEntries.push({
|
||||
line: `[ERROR] Could not read ${sourcePath}: ${err.message}`,
|
||||
lineNumber: 0,
|
||||
source: sourcePath,
|
||||
timestamp: new Date().toISOString(),
|
||||
level: 'ERROR',
|
||||
});
|
||||
}
|
||||
}
|
||||
allEntries.sort((a, b) => {
|
||||
if (a.timestamp && b.timestamp)
|
||||
return a.timestamp.localeCompare(b.timestamp);
|
||||
return 0;
|
||||
});
|
||||
return allEntries;
|
||||
}
|
||||
readFileContent(filePath, type) {
|
||||
switch (type) {
|
||||
case 'gzip': return this.readGzip(filePath);
|
||||
case 'bzip2': return this.readBzip2(filePath);
|
||||
case 'xz': return this.readXz(filePath);
|
||||
case 'zstd': return this.readZstd(filePath);
|
||||
default: return fs.readFileSync(filePath, 'utf-8');
|
||||
}
|
||||
}
|
||||
readGzip(filePath) {
|
||||
const compressed = fs.readFileSync(filePath);
|
||||
return zlib.gunzipSync(compressed).toString('utf-8');
|
||||
}
|
||||
readBzip2(filePath) {
|
||||
try {
|
||||
return (0, child_process_1.execSync)(`bzcat "${filePath}"`, { maxBuffer: 100 * 1024 * 1024 }).toString('utf-8');
|
||||
}
|
||||
catch {
|
||||
throw new Error('Cannot decompress bzip2. Make sure bzip2 is installed.');
|
||||
}
|
||||
}
|
||||
readXz(filePath) {
|
||||
try {
|
||||
return (0, child_process_1.execSync)(`xzcat "${filePath}"`, { maxBuffer: 100 * 1024 * 1024 }).toString('utf-8');
|
||||
}
|
||||
catch {
|
||||
throw new Error('Cannot decompress xz. Make sure xz-utils is installed.');
|
||||
}
|
||||
}
|
||||
readZstd(filePath) {
|
||||
try {
|
||||
return (0, child_process_1.execSync)(`zstdcat "${filePath}"`, { maxBuffer: 100 * 1024 * 1024 }).toString('utf-8');
|
||||
}
|
||||
catch {
|
||||
throw new Error('Cannot decompress zstd. Make sure zstd is installed.');
|
||||
}
|
||||
}
|
||||
extractTimestamp(line, customRegex) {
|
||||
const patterns = [
|
||||
/(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)/,
|
||||
/^([A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})/,
|
||||
/\[(\d{2}\/[A-Z][a-z]{2}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+-]\d{4})\]/,
|
||||
/(\d{4}\/\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2})/,
|
||||
];
|
||||
if (customRegex) {
|
||||
try {
|
||||
const match = line.match(new RegExp(customRegex));
|
||||
if (match)
|
||||
return match[1] || match[0];
|
||||
}
|
||||
catch { /* skip */ }
|
||||
}
|
||||
for (const pattern of patterns) {
|
||||
const match = line.match(pattern);
|
||||
if (match)
|
||||
return match[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
extractLevel(line) {
|
||||
const upper = line.toUpperCase();
|
||||
const levels = ['FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'TRACE', 'VERBOSE'];
|
||||
for (const level of levels) {
|
||||
if (new RegExp(`\\b${level}\\b`).test(upper)) {
|
||||
return level === 'WARNING' ? 'WARN' : level;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
resolveGlob(pattern) {
|
||||
const dir = path.dirname(pattern);
|
||||
const filePattern = path.basename(pattern);
|
||||
const results = [];
|
||||
try {
|
||||
const files = fs.readdirSync(dir);
|
||||
const regex = new RegExp('^' + filePattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
||||
for (const file of files) {
|
||||
if (regex.test(file)) {
|
||||
const fullPath = path.join(dir, file);
|
||||
try {
|
||||
if (fs.statSync(fullPath).isFile())
|
||||
results.push(fullPath);
|
||||
}
|
||||
catch { /* skip */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* dir doesn't exist */ }
|
||||
return results;
|
||||
}
|
||||
async getFileStats(sourcePath) {
|
||||
const resolved = path.resolve(sourcePath);
|
||||
try {
|
||||
const stats = fs.statSync(resolved);
|
||||
const source = this.sources.get(resolved);
|
||||
const content = this.readFileContent(resolved, source?.type || 'text');
|
||||
return {
|
||||
size: stats.size,
|
||||
lastModified: stats.mtime.toISOString(),
|
||||
lineCount: content.split('\n').length,
|
||||
type: source?.type || 'text',
|
||||
};
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.LogSourceManager = LogSourceManager;
|
||||
//# sourceMappingURL=logSourceManager.js.map
|
||||
Reference in New Issue
Block a user