Skip to main content
Glama
logger.js9.35 kB
import { appendFileSync, existsSync, mkdirSync } from 'fs'; import { join } from 'path'; 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 { config; logFilePath; errorFilePath; correlationMap = new Map(); constructor(config = {}) { this.config = { level: LogLevel.INFO, enableConsole: true, enableFile: true, logDirectory: join(process.cwd(), 'logs'), maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 5, enableStructuredLogging: true, enableCorrelation: true, ...config }; // Ensure log directory exists if (this.config.enableFile && !existsSync(this.config.logDirectory)) { mkdirSync(this.config.logDirectory, { recursive: true }); } const timestamp = new Date().toISOString().split('T')[0]; this.logFilePath = join(this.config.logDirectory, `aem-mcp-${timestamp}.log`); this.errorFilePath = join(this.config.logDirectory, `aem-mcp-errors-${timestamp}.log`); } shouldLog(level) { return level <= this.config.level; } formatMessage(entry) { if (this.config.enableStructuredLogging) { return JSON.stringify(entry); } else { const levelName = LogLevel[entry.level]; let message = `[${entry.timestamp}] ${levelName}`; if (entry.context) { message += ` [${entry.context}]`; } if (entry.method) { message += ` [${entry.method}]`; } if (entry.requestId) { message += ` [${entry.requestId}]`; } message += `: ${entry.message}`; if (entry.duration !== undefined) { message += ` (${entry.duration}ms)`; } if (entry.error) { message += `\n Error: ${entry.error.message || entry.error}`; if (entry.error.stack) { message += `\n Stack: ${entry.error.stack}`; } } if (entry.metadata) { message += `\n Metadata: ${JSON.stringify(entry.metadata, null, 2)}`; } return message; } } writeToFile(entry) { if (!this.config.enableFile) return; try { const message = this.formatMessage(entry) + '\n'; appendFileSync(this.logFilePath, message, 'utf8'); // Also write errors to separate error log if (entry.level === LogLevel.ERROR) { appendFileSync(this.errorFilePath, message, 'utf8'); } // TODO: Implement log rotation based on file size } catch (error) { console.error('Failed to write to log file:', error); } } writeToConsole(entry) { if (!this.config.enableConsole) return; const message = this.formatMessage(entry); switch (entry.level) { case LogLevel.ERROR: console.error(message); break; case LogLevel.WARN: console.warn(message); break; case LogLevel.DEBUG: case LogLevel.TRACE: console.debug(message); break; default: console.log(message); } } log(level, message, context) { if (!this.shouldLog(level)) return; const entry = { timestamp: new Date().toISOString(), level, message, ...context }; this.writeToConsole(entry); this.writeToFile(entry); } error(message, context) { this.log(LogLevel.ERROR, message, context); } warn(message, context) { this.log(LogLevel.WARN, message, context); } info(message, context) { this.log(LogLevel.INFO, message, context); } debug(message, context) { this.log(LogLevel.DEBUG, message, context); } trace(message, context) { this.log(LogLevel.TRACE, message, context); } // Method-specific logging helpers methodStart(method, parameters, requestId) { this.info(`Method started: ${method}`, { method, requestId, metadata: { parameters } }); } methodEnd(method, duration, success, requestId, result) { const level = success ? LogLevel.INFO : LogLevel.ERROR; const message = `Method ${success ? 'completed' : 'failed'}: ${method}`; this.log(level, message, { method, requestId, duration, metadata: success ? { resultSize: JSON.stringify(result || {}).length } : undefined }); } methodError(method, error, duration, requestId, parameters) { this.error(`Method error: ${method}`, { method, requestId, duration, error, metadata: { parameters } }); } // HTTP request logging httpRequest(method, url, statusCode, duration, requestId) { const level = statusCode >= 400 ? LogLevel.WARN : LogLevel.INFO; this.log(level, `HTTP ${method} ${url} - ${statusCode}`, { context: 'HTTP', requestId, duration, metadata: { method, url, statusCode } }); } // AEM-specific logging aemOperation(operation, path, success, duration, requestId, details) { const level = success ? LogLevel.INFO : LogLevel.ERROR; this.log(level, `AEM ${operation}: ${path} - ${success ? 'SUCCESS' : 'FAILED'}`, { context: 'AEM', requestId, duration, metadata: { operation, path, details } }); } // Correlation ID management setCorrelation(requestId, userId) { if (this.config.enableCorrelation && userId) { this.correlationMap.set(requestId, userId); } } getCorrelation(requestId) { return this.correlationMap.get(requestId); } clearCorrelation(requestId) { this.correlationMap.delete(requestId); } // Performance monitoring performance(operation, duration, metadata) { const level = duration > 5000 ? LogLevel.WARN : LogLevel.INFO; // Warn if operation takes > 5s this.log(level, `Performance: ${operation} took ${duration}ms`, { context: 'PERFORMANCE', duration, metadata }); } // Security logging security(event, details, requestId) { this.warn(`Security event: ${event}`, { context: 'SECURITY', requestId, metadata: details }); } // System health logging health(component, status, details) { const level = status === 'healthy' ? LogLevel.INFO : status === 'degraded' ? LogLevel.WARN : LogLevel.ERROR; this.log(level, `Health check: ${component} is ${status}`, { context: 'HEALTH', metadata: { component, status, ...details } }); } // Configuration and utility methods setLevel(level) { this.config.level = level; this.info(`Log level changed to ${LogLevel[level]}`); } getLevel() { return this.config.level; } flush() { // In a real implementation, this would flush any buffered logs this.info('Log flush requested'); } // Create child logger with context child(context) { const childLogger = new Logger(this.config); // Override log method to include context const originalLog = childLogger.log.bind(childLogger); childLogger.log = (level, message, logContext) => { originalLog(level, message, { ...logContext, context }); }; return childLogger; } } // Global logger instance export const logger = new Logger({ level: process.env.LOG_LEVEL ? parseInt(process.env.LOG_LEVEL) : LogLevel.INFO, enableConsole: process.env.NODE_ENV !== 'test', enableFile: true, enableStructuredLogging: process.env.STRUCTURED_LOGGING === 'true' }); // Request ID generator export function generateRequestId() { return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } // Logging middleware for Express export function loggingMiddleware(req, res, next) { const requestId = generateRequestId(); const startTime = Date.now(); req.requestId = requestId; req.logger = logger.child(`HTTP-${req.method}`); // Log request start logger.httpRequest(req.method, req.url, 0, 0, requestId); // Override res.end to log response const originalEnd = res.end; res.end = function (...args) { const duration = Date.now() - startTime; logger.httpRequest(req.method, req.url, res.statusCode, duration, requestId); originalEnd.apply(res, args); }; next(); } export default logger; //# sourceMappingURL=logger.js.map

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/indrasishbanerjee/aem-mcp-server'

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