Skip to main content
Glama
LogManager.ts3.93 kB
import { ChildProcess } from 'child_process'; import { LogEntry } from '../types.js'; import { Logger } from '../utils/logger.js'; export class LogManager { private logger = Logger.getInstance(); private logs: LogEntry[] = []; private logStream: NodeJS.ReadableStream | null = null; private readonly maxLogs = 1000; async startLogging(process: ChildProcess): Promise<void> { this.logger.info('Starting log monitoring'); if (process.stdout) { process.stdout.on('data', (data: Buffer) => { this.addLog('info', data.toString(), 'stdout'); }); } if (process.stderr) { process.stderr.on('data', (data: Buffer) => { this.addLog('error', data.toString(), 'stderr'); }); } process.on('error', (error) => { this.addLog('error', `Process error: ${error.message}`, 'stderr'); }); process.on('exit', (code, signal) => { const message = signal ? `Process exited with signal ${signal}` : `Process exited with code ${code}`; this.addLog('info', message, 'stdout'); }); } async stopLogging(): Promise<void> { this.logger.info('Stopping log monitoring'); this.logStream = null; } async getLogs(lines?: number): Promise<LogEntry[]> { const requestedLines = lines || 50; const totalLogs = this.logs.length; if (requestedLines >= totalLogs) { return [...this.logs]; } return this.logs.slice(totalLogs - requestedLines); } async clearLogs(): Promise<void> { this.logs = []; this.logger.debug('Logs cleared'); } private addLog(level: 'info' | 'error' | 'warn', message: string, source: 'stdout' | 'stderr'): void { // Parse the actual log level from the message content const parsedLevel = this.parseLogLevel(message); const logEntry: LogEntry = { timestamp: new Date(), level: parsedLevel || level, message: message.trim(), source }; this.logs.push(logEntry); // Maintain ring buffer by removing old logs if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(this.logs.length - this.maxLogs); } // Also output to internal logger for debugging this.logger.debug(`[${source}] ${logEntry.message}`); } private parseLogLevel(message: string): 'info' | 'error' | 'warn' | null { const lowerMessage = message.toLowerCase(); // Common error patterns if (lowerMessage.includes('error') || lowerMessage.includes('failed') || lowerMessage.includes('cannot') || lowerMessage.includes('unable') || lowerMessage.includes('exception')) { return 'error'; } // Common warning patterns if (lowerMessage.includes('warn') || lowerMessage.includes('warning') || lowerMessage.includes('deprecated') || lowerMessage.includes('notice')) { return 'warn'; } // Check for log level prefixes const logLevelRegex = /\[(error|warn|warning|info|debug)\]/i; const match = message.match(logLevelRegex); if (match) { const level = match[1].toLowerCase(); if (level === 'warning') return 'warn'; if (['error', 'warn', 'info'].includes(level)) { return level as 'error' | 'warn' | 'info'; } } return null; } getLogStats(): { total: number; errors: number; warnings: number; info: number } { const stats = { total: this.logs.length, errors: 0, warnings: 0, info: 0 }; for (const log of this.logs) { if (log.level === 'error') stats.errors++; else if (log.level === 'warn') stats.warnings++; else stats.info++; } return stats; } hasRecentErrors(minutes: number = 5): boolean { const cutoff = new Date(Date.now() - minutes * 60 * 1000); return this.logs.some(log => log.level === 'error' && log.timestamp > cutoff ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/masamunet/npm-dev-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server