loganalyzer-mcp
Version:
🚀 Debug server logs in under 30 seconds - AI-powered MCP server with rapid debugging, real-time monitoring, and actionable fixes
151 lines • 5.8 kB
JavaScript
import * as chokidar from 'chokidar';
import * as fs from 'fs/promises';
import { LogAnalyzer } from './logAnalyzer.js';
import { LogUtils } from '../utils.js';
export class FileWatcher {
watchers = new Map();
logAnalyzer;
constructor() {
this.logAnalyzer = new LogAnalyzer();
}
async watchLogFile(filePath, options = {}) {
// Validate file path first
const validation = await LogUtils.validateFilePath(filePath);
if (!validation.valid) {
throw new Error(`Cannot watch file: ${validation.error}`);
}
// Stop existing watcher if present
if (this.watchers.has(filePath)) {
await this.stopWatching(filePath);
}
const watchOptions = {
pollInterval: options.pollInterval || 1000,
ignoreInitial: options.ignoreInitial ?? false,
usePolling: options.usePolling ?? true
};
// Get initial file size
const stats = await fs.stat(filePath);
const initialSize = stats.size;
// Create watcher
const watcher = chokidar.watch(filePath, {
ignoreInitial: watchOptions.ignoreInitial,
usePolling: watchOptions.usePolling,
interval: watchOptions.pollInterval
});
const watchedFile = {
filePath,
watcher,
lastSize: initialSize,
errors: [],
lastUpdate: new Date(),
options: watchOptions
};
// Set up event handlers
watcher.on('change', async (path) => {
await this.handleFileChange(path);
});
watcher.on('error', (error) => {
console.error(`File watcher error for ${filePath}:`, error);
});
this.watchers.set(filePath, watchedFile);
// Process initial content if not ignoring initial
if (!watchOptions.ignoreInitial) {
await this.handleFileChange(filePath);
}
}
async stopWatching(filePath) {
const watchedFile = this.watchers.get(filePath);
if (!watchedFile) {
throw new Error(`File ${filePath} is not being watched`);
}
await watchedFile.watcher.close();
this.watchers.delete(filePath);
}
async listWatchedFiles() {
const results = [];
for (const [filePath, watchedFile] of this.watchers) {
results.push({
filePath,
newErrors: watchedFile.errors.slice(-5), // Last 5 errors
totalErrors: watchedFile.errors.length,
lastUpdate: watchedFile.lastUpdate
});
}
return results;
}
async getRecentErrors(filePath, limit = 10) {
if (filePath) {
const watchedFile = this.watchers.get(filePath);
if (!watchedFile) {
throw new Error(`File ${filePath} is not being watched`);
}
return watchedFile.errors.slice(-limit);
}
// Get recent errors from all watched files
const allErrors = [];
for (const watchedFile of this.watchers.values()) {
allErrors.push(...watchedFile.errors);
}
// Sort by timestamp and return most recent
return allErrors
.sort((a, b) => b.metadata.timestamp.getTime() - a.metadata.timestamp.getTime())
.slice(0, limit);
}
async handleFileChange(filePath) {
const watchedFile = this.watchers.get(filePath);
if (!watchedFile) {
return;
}
try {
// Get current file size
const stats = await fs.stat(filePath);
const currentSize = stats.size;
// Only process if file has grown (new content added)
if (currentSize <= watchedFile.lastSize) {
return;
}
// Read only the new content
const newContent = await this.readNewContent(filePath, watchedFile.lastSize, currentSize);
if (newContent.trim()) {
// Check if new content contains errors
const errorPatterns = LogUtils.extractErrorPatterns(newContent);
if (errorPatterns.length > 0) {
// Analyze the new errors
const analysis = await this.logAnalyzer.analyzeLogs(newContent, {
logFormat: 'auto',
contextLines: 20
});
// Store the analysis
watchedFile.errors.push(analysis);
// Keep only last 100 errors to prevent memory issues
if (watchedFile.errors.length > 100) {
watchedFile.errors = watchedFile.errors.slice(-100);
}
console.error(`New error detected in ${filePath}: ${analysis.rootCause}`);
}
}
// Update tracking info
watchedFile.lastSize = currentSize;
watchedFile.lastUpdate = new Date();
}
catch (error) {
console.error(`Error processing file change for ${filePath}:`, error);
}
}
async readNewContent(filePath, startByte, endByte) {
const fileHandle = await fs.open(filePath, 'r');
try {
const buffer = Buffer.alloc(endByte - startByte);
await fileHandle.read(buffer, 0, buffer.length, startByte);
return buffer.toString('utf8');
}
finally {
await fileHandle.close();
}
}
async stopAll() {
const stopPromises = Array.from(this.watchers.keys()).map(filePath => this.stopWatching(filePath));
await Promise.all(stopPromises);
}
}
//# sourceMappingURL=fileWatcher.js.map