Skip to main content
Glama
Logger.ts5.82 kB
import fs from 'fs'; import path from 'path'; import { getMcpPaths } from './PathUtils.js'; export enum LogLevel { DEBUG = 'DEBUG', INFO = 'INFO', WARN = 'WARN', ERROR = 'ERROR' } export interface LogEntry { timestamp: string; level: LogLevel; message: string; context?: Record<string, any>; error?: Error; } export class Logger { private static instance: Logger; private logFile: string; private writeStream: fs.WriteStream | null = null; private consoleEnabled = true; private fileEnabled = true; private constructor() { const mcpPaths = getMcpPaths(); this.logFile = mcpPaths.logs; // Initialize file stream try { this.writeStream = fs.createWriteStream(this.logFile, { flags: 'a', // append mode encoding: 'utf8' }); this.writeStream.on('error', (error) => { console.error('[LOGGER ERROR] Failed to write to log file:', error.message); this.fileEnabled = false; }); } catch (error: any) { console.error('[LOGGER ERROR] Failed to create log file stream:', error.message); this.fileEnabled = false; } } static getInstance(): Logger { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } private formatLogEntry(level: LogLevel, message: string, context?: Record<string, any>, error?: Error): string { const timestamp = new Date().toISOString(); const entry: LogEntry = { timestamp, level, message, context, ...(error && { error: { message: error.message, stack: error.stack, name: error.name } as any }) }; return JSON.stringify(entry, null, 0); // Compact JSON for log files } private formatConsoleEntry(level: LogLevel, message: string): string { const timestamp = new Date().toISOString(); const prefix = `[${timestamp}] ${level}:`; return `${prefix} ${message}`; } private writeToFile(message: string): void { if (!this.fileEnabled || !this.writeStream) return; try { this.writeStream.write(message + '\n'); } catch (error: any) { console.error('[LOGGER ERROR] Write to file failed:', error.message); } } private writeToConsole(level: LogLevel, message: string): void { if (!this.consoleEnabled) return; const formattedMessage = this.formatConsoleEntry(level, message); switch (level) { case LogLevel.ERROR: console.error(`\x1b[31m${formattedMessage}\x1b[0m`); // Red break; case LogLevel.WARN: console.warn(`\x1b[33m${formattedMessage}\x1b[0m`); // Yellow break; case LogLevel.INFO: console.info(`\x1b[36m${formattedMessage}\x1b[0m`); // Cyan break; case LogLevel.DEBUG: console.debug(`\x1b[35m${formattedMessage}\x1b[0m`); // Magenta break; default: console.log(formattedMessage); } } debug(message: string, context?: Record<string, any>): void { const logMessage = this.formatLogEntry(LogLevel.DEBUG, message, context); this.writeToFile(logMessage); this.writeToConsole(LogLevel.DEBUG, message); } info(message: string, context?: Record<string, any>): void { const logMessage = this.formatLogEntry(LogLevel.INFO, message, context); this.writeToFile(logMessage); this.writeToConsole(LogLevel.INFO, message); } warn(message: string, context?: Record<string, any>, error?: Error): void { const logMessage = this.formatLogEntry(LogLevel.WARN, message, context, error); this.writeToFile(logMessage); this.writeToConsole(LogLevel.WARN, message); } error(message: string, error?: Error, context?: Record<string, any>): void { const logMessage = this.formatLogEntry(LogLevel.ERROR, message, context, error); this.writeToFile(logMessage); this.writeToConsole(LogLevel.ERROR, message); } // Performance measurement helper time(label: string): () => void { const startTime = process.hrtime.bigint(); this.debug(`Started timing: ${label}`); return () => { const endTime = process.hrtime.bigint(); const durationMs = Number(endTime - startTime) / 1_000_000; // Convert to milliseconds this.info(`Completed timing: ${label}`, { duration: `${durationMs.toFixed(2)}ms` }); return durationMs; }; } // Utility methods enableConsole(enabled = true): void { this.consoleEnabled = enabled; } enableFile(enabled = true): void { this.fileEnabled = enabled; } getLogFile(): string { return this.logFile; } // Check if log file exists and get stats getLogStats(): { exists: boolean; size?: number; modified?: Date } { try { const stats = fs.statSync(this.logFile); return { exists: true, size: stats.size, modified: stats.mtime }; } catch { return { exists: false }; } } // Close the file stream (for cleanup) close(): void { if (this.writeStream) { try { this.writeStream.end(); this.writeStream = null; } catch (error: any) { console.error('[LOGGER ERROR] Failed to close log stream:', error.message); } } } } // Export singleton instance export const logger = Logger.getInstance(); // Export convenience functions export const log = { debug: (message: string, context?: Record<string, any>) => logger.debug(message, context), info: (message: string, context?: Record<string, any>) => logger.info(message, context), warn: (message: string, context?: Record<string, any>, error?: Error) => logger.warn(message, context, error), error: (message: string, error?: Error, context?: Record<string, any>) => logger.error(message, error, context), time: (label: string) => logger.time(label) };

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/PatrickRuddiman/local-search-mcp'

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