UNPKG

log-vista

Version:

LogVista Agent - Lightweight system monitoring and log collection for any project/language

280 lines (233 loc) 8.48 kB
const fs = require('fs').promises; const path = require('path'); const logger = require('./logger'); const MetricsCollector = require('./metricsCollector'); const LogCollector = require('./logCollector'); const Uploader = require('./uploader'); const Scheduler = require('./scheduler'); class LogVistaAgent { constructor() { this.config = null; this.metricsCollector = null; this.logCollector = null; this.uploader = null; this.scheduler = null; this.isRunning = false; } async init() { try { logger.info('Initializing LogVista Agent...'); // Load configuration await this.loadConfig(); // Initialize components this.metricsCollector = new MetricsCollector(); this.logCollector = new LogCollector(); this.uploader = new Uploader(this.config); this.scheduler = new Scheduler( this.metricsCollector, this.logCollector, this.uploader, this.config ); // Test connection to central system const connected = await this.uploader.testConnection(); if (!connected) { logger.warn('Unable to connect to central system. Agent will continue and retry periodically.'); } logger.info('LogVista Agent initialized successfully'); } catch (error) { logger.error('Failed to initialize LogVista Agent:', error); throw error; } } async loadConfig() { try { let configPath; // Priority order for config files: // 1. Environment variable (set by CLI) // 2. Local project .logvista/config.json // 3. Legacy config/agent.config.json // 4. Global config if (process.env.LOGVISTA_CONFIG_PATH) { configPath = process.env.LOGVISTA_CONFIG_PATH; } else { const localConfigPath = path.join(process.cwd(), '.logvista', 'config.json'); const legacyConfigPath = path.join(__dirname, '../config/agent.config.json'); const os = require('os'); const globalConfigPath = path.join( os.platform() === 'win32' ? path.join(os.homedir(), 'AppData', 'Roaming', 'LogVista') : path.join(os.homedir(), '.logvista'), 'agent.config.json' ); if (await fs.access(localConfigPath).then(() => true).catch(() => false)) { configPath = localConfigPath; } else if (await fs.access(legacyConfigPath).then(() => true).catch(() => false)) { configPath = legacyConfigPath; } else if (await fs.access(globalConfigPath).then(() => true).catch(() => false)) { configPath = globalConfigPath; } else { throw new Error('No configuration file found. Run "logvista init" to create one.'); } } logger.info(`Loading configuration from: ${configPath}`); const configContent = await fs.readFile(configPath, 'utf8'); this.config = JSON.parse(configContent); // Validate required configuration this.validateConfig(); logger.info('Configuration loaded successfully'); logger.debug('Config:', { projects: this.config.projects.length, interval: this.config.collection?.interval || this.config.agent?.collection_interval || 30, centralUrl: this.config.central_system.url }); } catch (error) { logger.error('Failed to load configuration:', error); throw new Error(`Configuration error: ${error.message}`); } } validateConfig() { // Flexible validation for both old and new config formats const required = ['central_system', 'projects']; for (const field of required) { if (!this.config[field]) { throw new Error(`Missing required configuration: ${field}`); } } if (!this.config.central_system.url) { throw new Error('Missing central_system.url in configuration'); } if (!this.config.central_system.token) { logger.warn('Missing central_system.token in configuration - you may need to set this'); } // Ensure collection config exists (support both old and new format) if (!this.config.collection && !this.config.agent) { this.config.collection = { interval: 30, batch_size: 100, retry_attempts: 3, retry_delay: 5000 }; } if (!Array.isArray(this.config.projects) || this.config.projects.length === 0) { throw new Error('At least one project must be configured'); } // Validate each project for (const project of this.config.projects) { if (!project.project_name) { throw new Error('Missing project_name in project configuration'); } if (!project.pwd_path) { throw new Error('Missing pwd_path in project configuration'); } } } async start() { try { if (this.isRunning) { logger.warn('Agent is already running'); return; } logger.info('Starting LogVista Agent...'); // Start log watching await this.logCollector.startWatching(this.config.projects); // Start scheduler this.scheduler.start(); this.isRunning = true; logger.info('LogVista Agent started successfully'); // Log agent status this.logStatus(); } catch (error) { logger.error('Failed to start LogVista Agent:', error); throw error; } } async stop() { try { if (!this.isRunning) { logger.warn('Agent is not running'); return; } logger.info('Stopping LogVista Agent...'); // Stop scheduler if (this.scheduler) { this.scheduler.stop(); } // Stop log watching if (this.logCollector) { this.logCollector.stopWatching(); } this.isRunning = false; logger.info('LogVista Agent stopped successfully'); } catch (error) { logger.error('Error stopping LogVista Agent:', error); throw error; } } logStatus() { const status = { isRunning: this.isRunning, scheduler: this.scheduler ? this.scheduler.getStatus() : null, projects: this.config.projects.filter(p => p.enabled).map(p => ({ name: p.project_name, path: p.pwd_path, logPaths: p.custom_log_paths?.length || 0 })), config: { collectionInterval: this.config.agent.collection_interval, batchSize: this.config.agent.batch_size, centralUrl: this.config.central_system.url } }; logger.info('Agent Status:', status); } async getStatus() { return { isRunning: this.isRunning, scheduler: this.scheduler ? this.scheduler.getStatus() : null, projects: this.config?.projects?.filter(p => p.enabled) || [], uptime: process.uptime(), memory: process.memoryUsage(), version: require('../package.json').version }; } } // Main execution async function main() { const agent = new LogVistaAgent(); try { await agent.init(); await agent.start(); // Graceful shutdown handlers const gracefulShutdown = async (signal) => { logger.info(`Received ${signal}, shutting down gracefully...`); try { await agent.stop(); process.exit(0); } catch (error) { logger.error('Error during shutdown:', error); process.exit(1); } }; process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Handle uncaught exceptions process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', error); gracefulShutdown('uncaughtException'); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection at:', promise, 'reason:', reason); gracefulShutdown('unhandledRejection'); }); } catch (error) { logger.error('Failed to start agent:', error); process.exit(1); } } // Export for potential use as module module.exports = LogVistaAgent; // Run if this file is executed directly if (require.main === module) { main(); }