UNPKG

claude-computer-use-mcp

Version:

MCP server providing browser automation capabilities to Claude Code

220 lines 8.45 kB
import * as fs from 'fs/promises'; import * as path from 'path'; import { EventEmitter } from 'events'; export class MonitoringService extends EventEmitter { metricsBuffer = []; auditLogBuffer = []; logDir; enableMetrics; enableAuditLogging; flushInterval; constructor(options = {}) { super(); this.logDir = options.logDir || './logs'; this.enableMetrics = options.enableMetrics || false; this.enableAuditLogging = options.enableAuditLogging || false; // Flush buffers periodically this.flushInterval = setInterval(() => this.flush(), options.flushIntervalMs || 30000); this.initialize(); } async initialize() { try { await fs.mkdir(this.logDir, { recursive: true }); await fs.chmod(this.logDir, 0o700); } catch (error) { console.error('Failed to initialize monitoring service:', error); } } recordMetric(metric) { if (!this.enableMetrics) return; const metricWithTimestamp = { ...metric, timestamp: Date.now() }; this.metricsBuffer.push(metricWithTimestamp); this.emit('metric', metricWithTimestamp); // Auto-flush if buffer gets too large if (this.metricsBuffer.length >= 100) { this.flush(); } } auditLog(entry) { if (!this.enableAuditLogging) return; const logEntry = { ...entry, timestamp: Date.now() }; this.auditLogBuffer.push(logEntry); this.emit('audit', logEntry); // Console output for important events if (['error', 'warn'].includes(entry.level)) { console.log(`[${entry.level.toUpperCase()}] ${entry.action}:`, entry.details); } // Auto-flush if buffer gets too large if (this.auditLogBuffer.length >= 50) { this.flush(); } } async flush() { try { if (this.metricsBuffer.length > 0) { await this.flushMetrics(); } if (this.auditLogBuffer.length > 0) { await this.flushAuditLogs(); } } catch (error) { console.error('Failed to flush monitoring data:', error); } } async flushMetrics() { const metrics = [...this.metricsBuffer]; this.metricsBuffer = []; const date = new Date().toISOString().split('T')[0]; const filename = `metrics-${date}.jsonl`; const filepath = path.join(this.logDir, filename); const lines = metrics.map(metric => JSON.stringify(metric)).join('\n') + '\n'; await fs.appendFile(filepath, lines, 'utf8'); } async flushAuditLogs() { const logs = [...this.auditLogBuffer]; this.auditLogBuffer = []; const date = new Date().toISOString().split('T')[0]; const filename = `audit-${date}.jsonl`; const filepath = path.join(this.logDir, filename); const lines = logs.map(log => JSON.stringify(log)).join('\n') + '\n'; await fs.appendFile(filepath, lines, 'utf8'); } async getMetrics(fromDate, toDate) { const metrics = []; try { const files = await fs.readdir(this.logDir); const metricsFiles = files.filter(f => f.startsWith('metrics-') && f.endsWith('.jsonl')); for (const file of metricsFiles) { const content = await fs.readFile(path.join(this.logDir, file), 'utf8'); const lines = content.trim().split('\n').filter(line => line.trim()); for (const line of lines) { try { const metric = JSON.parse(line); if (fromDate && metric.timestamp < fromDate.getTime()) continue; if (toDate && metric.timestamp > toDate.getTime()) continue; metrics.push(metric); } catch (error) { console.warn('Failed to parse metric line:', line); } } } } catch (error) { console.error('Failed to read metrics:', error); } return metrics.sort((a, b) => a.timestamp - b.timestamp); } async getAuditLogs(fromDate, toDate, level) { const logs = []; try { const files = await fs.readdir(this.logDir); const auditFiles = files.filter(f => f.startsWith('audit-') && f.endsWith('.jsonl')); for (const file of auditFiles) { const content = await fs.readFile(path.join(this.logDir, file), 'utf8'); const lines = content.trim().split('\n').filter(line => line.trim()); for (const line of lines) { try { const log = JSON.parse(line); if (fromDate && log.timestamp < fromDate.getTime()) continue; if (toDate && log.timestamp > toDate.getTime()) continue; if (level && log.level !== level) continue; logs.push(log); } catch (error) { console.warn('Failed to parse audit log line:', line); } } } } catch (error) { console.error('Failed to read audit logs:', error); } return logs.sort((a, b) => a.timestamp - b.timestamp); } generateReport(sessionId) { return this.getMetrics().then(metrics => { const filteredMetrics = sessionId ? metrics.filter(m => m.sessionId === sessionId) : metrics; const totalRequests = filteredMetrics.length; const successfulRequests = filteredMetrics.filter(m => m.success).length; const successRate = totalRequests > 0 ? successfulRequests / totalRequests : 0; const totalDuration = filteredMetrics.reduce((sum, m) => sum + m.duration, 0); const averageResponseTime = totalRequests > 0 ? totalDuration / totalRequests : 0; const errorTypes = {}; const actionCounts = {}; filteredMetrics.forEach(metric => { if (metric.error) { errorTypes[metric.error] = (errorTypes[metric.error] || 0) + 1; } actionCounts[metric.action] = (actionCounts[metric.action] || 0) + 1; }); const topActions = Object.entries(actionCounts) .map(([action, count]) => ({ action, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); return { totalRequests, successRate, averageResponseTime, errorTypes, topActions }; }); } async cleanup() { clearInterval(this.flushInterval); await this.flush(); } } // Singleton instance export const monitoring = new MonitoringService(); // Performance monitoring decorator export function monitored(action) { return function (target, propertyName, descriptor) { const method = descriptor.value; descriptor.value = async function (...args) { const startTime = Date.now(); let success = true; let error; try { const result = await method.apply(this, args); return result; } catch (err) { success = false; error = err instanceof Error ? err.message : 'Unknown error'; throw err; } finally { const duration = Date.now() - startTime; monitoring.recordMetric({ sessionId: args[0] || 'unknown', action: `${target.constructor.name}.${propertyName}`, duration, success, error, metadata: { action } }); } }; return descriptor; }; } //# sourceMappingURL=monitoring.js.map