UNPKG

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
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