Skip to main content
Glama
logger.ts5.53 kB
/** * @fileOverview: Simple structured logger for the MCP Server with file and console output * @module: Logger * @keyFunctions: * - info(): Log informational messages with context * - warn(): Log warning messages with context * - error(): Log error messages with context * - debug(): Log debug messages with environment-based filtering * @dependencies: * - fs: File system operations for log file management * - path: Path manipulation for log file location * - console: Standard output for immediate logging * @context: Provides structured logging with both console and file output, ensuring logs are available in MCP stdio environments while maintaining persistent log files for debugging */ import * as fs from 'fs'; import * as path from 'path'; export interface LogContext { [key: string]: any; } export class Logger { private prefix: string; private logFilePath: string | null; private readonly maxSizeBytes = 10 * 1024 * 1024; // 10 MB private readonly maxArchives = 3; constructor(prefix: string = 'MCP') { this.prefix = prefix; this.logFilePath = this.initializeFileLogging(); } info(message: string, context?: LogContext): void { if (this.shouldLog('info')) { this.log('INFO', message, context); } } warn(message: string, context?: LogContext): void { this.log('WARN', message, context); } error(message: string, context?: LogContext): void { this.log('ERROR', message, context); } debug(message: string, context?: LogContext): void { if (process.env.DEBUG || process.env.NODE_ENV === 'development') { this.log('DEBUG', message, context); } } private shouldLog(level: string): boolean { const logLevel = process.env.LOG_LEVEL?.toLowerCase() || 'info'; const levels = ['debug', 'info', 'warn', 'error']; const currentLevelIndex = levels.indexOf(logLevel); const messageLevelIndex = levels.indexOf(level.toLowerCase()); // Log if message level is at or above current log level return messageLevelIndex >= currentLevelIndex; } private log(level: string, message: string, context?: LogContext): void { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level, prefix: this.prefix, message, ...(context && { context }), }; const formattedMessage = `[${timestamp}] ${level} [${this.prefix}] ${message}`; const contextStr = context ? JSON.stringify(context) : ''; // IMPORTANT: Never write logs to stdout in MCP stdio mode; use stderr only switch (level) { case 'ERROR': console.error(formattedMessage, contextStr); break; case 'WARN': console.warn(formattedMessage, contextStr); break; case 'DEBUG': // Use console.debug for debug messages in tests, stderr in production if (process.env.NODE_ENV === 'test') { console.debug(formattedMessage, contextStr); } else { console.error(formattedMessage, contextStr); } break; default: // INFO level - use console.info in tests, stderr in production if (process.env.NODE_ENV === 'test') { console.info(formattedMessage, contextStr); } else { console.error(formattedMessage, contextStr); } } // Also write to file for environments where stdio is not surfaced (e.g., Cursor MCP) if (this.logFilePath) { try { this.rotateLogsIfNeeded(); const line = JSON.stringify(logEntry) + '\n'; fs.appendFileSync(this.logFilePath, line, { encoding: 'utf8' }); } catch { // best-effort; ignore file logging errors } } } private initializeFileLogging(): string | null { try { const home = process.env.USERPROFILE || process.env.HOME || process.cwd(); const dir = path.join(home, '.ambiance', 'logs'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const logPath = path.join(dir, 'mcp-proxy.log'); // Touch file if (!fs.existsSync(logPath)) { fs.writeFileSync(logPath, '', { encoding: 'utf8' }); } return logPath; } catch { return null; } } private rotateLogsIfNeeded(): void { if (!this.logFilePath) return; try { const stats = fs.existsSync(this.logFilePath) ? fs.statSync(this.logFilePath) : null; if (!stats || stats.size < this.maxSizeBytes) return; const base = this.logFilePath; const oldest = `${base}.${this.maxArchives}`; if (fs.existsSync(oldest)) { try { fs.unlinkSync(oldest); } catch { // best-effort; ignore file deletion errors } } for (let i = this.maxArchives - 1; i >= 1; i--) { const src = `${base}.${i}`; const dst = `${base}.${i + 1}`; if (fs.existsSync(src)) { try { fs.renameSync(src, dst); } catch { // best-effort; ignore file rename errors } } } if (fs.existsSync(base)) { try { fs.renameSync(base, `${base}.1`); } catch { // best-effort; ignore file rename errors } } try { fs.writeFileSync(base, '', { encoding: 'utf8' }); } catch { // best-effort; ignore file write errors } } catch { // ignore rotation failures } } } // Default logger instance export const logger = new Logger('Ambiance');

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/sbarron/AmbianceMCP'

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