mcp-grocy
Version:
Model Context Protocol (MCP) server for Grocy integration
142 lines (141 loc) • 4.66 kB
JavaScript
/**
* 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;
}
}