UNPKG

mcp-grocy

Version:

Model Context Protocol (MCP) server for Grocy integration

142 lines (141 loc) 4.66 kB
/** * Centralized logging utility with configurable levels and structured output */ export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["ERROR"] = 0] = "ERROR"; LogLevel[LogLevel["WARN"] = 1] = "WARN"; LogLevel[LogLevel["INFO"] = 2] = "INFO"; LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG"; LogLevel[LogLevel["TRACE"] = 4] = "TRACE"; })(LogLevel || (LogLevel = {})); export class Logger { static instance; logLevel; enabledCategories = null; constructor() { // Default to INFO level, but allow override via environment this.logLevel = this.parseLogLevel(process.env.LOG_LEVEL || 'INFO'); // Allow filtering by categories const categories = process.env.LOG_CATEGORIES; if (categories) { this.enabledCategories = new Set(categories.split(',').map(c => c.trim().toUpperCase())); } } static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } parseLogLevel(level) { const upperLevel = level.toUpperCase(); switch (upperLevel) { case 'ERROR': return LogLevel.ERROR; case 'WARN': return LogLevel.WARN; case 'INFO': return LogLevel.INFO; case 'DEBUG': return LogLevel.DEBUG; case 'TRACE': return LogLevel.TRACE; default: return LogLevel.INFO; } } shouldLog(level, category) { // Check log level if (level > this.logLevel) { return false; } // Check category filter if (this.enabledCategories && category) { return this.enabledCategories.has(category.toUpperCase()); } return true; } formatMessage(entry) { const timestamp = entry.timestamp.toISOString(); const level = LogLevel[entry.level].padEnd(5); const category = entry.category ? `[${entry.category}]` : ''; const data = entry.data ? ` ${JSON.stringify(entry.data)}` : ''; return `${timestamp} ${level} ${category} ${entry.message}${data}`; } log(level, message, category, data) { if (!this.shouldLog(level, category)) { return; } const entry = { level, message, category: category || undefined, data, timestamp: new Date() }; const formattedMessage = this.formatMessage(entry); // Use stderr for all logs to avoid interfering with MCP stdio protocol if (level <= LogLevel.ERROR) { console.error(formattedMessage); } else { console.error(formattedMessage); } } error(message, category, data) { this.log(LogLevel.ERROR, message, category, data); } warn(message, category, data) { this.log(LogLevel.WARN, message, category, data); } info(message, category, data) { this.log(LogLevel.INFO, message, category, data); } debug(message, category, data) { this.log(LogLevel.DEBUG, message, category, data); } trace(message, category, data) { this.log(LogLevel.TRACE, message, category, data); } // Convenience methods for common categories config(message, data) { this.info(message, 'CONFIG', data); } api(message, data) { this.debug(message, 'API', data); } module(message, data) { this.debug(message, 'MODULE', data); } tools(message, data) { this.info(message, 'TOOLS', data); } server(message, data) { this.info(message, 'SERVER', data); } // Testing utilities setLogLevel(level) { this.logLevel = level; } setEnabledCategories(categories) { this.enabledCategories = categories ? new Set(categories.map(c => c.toUpperCase())) : null; } } // Export singleton instance export const logger = Logger.getInstance(); // Convenience function for quick logging export function log(level, message, category, data) { // Access the private log method through the public methods switch (level) { case LogLevel.ERROR: logger.error(message, category, data); break; case LogLevel.WARN: logger.warn(message, category, data); break; case LogLevel.INFO: logger.info(message, category, data); break; case LogLevel.DEBUG: logger.debug(message, category, data); break; case LogLevel.TRACE: logger.trace(message, category, data); break; } }