Skip to main content
Glama

Vibe Coder MCP

by freshtechbro
logger.ts•10.9 kB
// src/logger.ts import { pino, Logger } from 'pino'; import path from 'path'; import { fileURLToPath } from 'url'; const isDevelopment = process.env.NODE_ENV === 'development'; const isStdioTransport = process.env.MCP_TRANSPORT === 'stdio' || process.argv.includes('--stdio'); const isInteractiveMode = process.argv.includes('--interactive'); const effectiveLogLevel = process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info'); /** * Enhanced transport detection function * Enhances existing transport detection logic to support all transport types * Following DRY principle by extending existing detection patterns */ export function detectTransportType(): 'cli' | 'stdio' | 'sse' | 'http' | 'websocket' { // CLI detection: Check if running via unified-cli or vibe command const isCli = process.argv[1]?.includes('unified-cli') || process.argv[1]?.includes('cli/index') || process.argv[1]?.includes('vibe') || process.env.VIBE_CLI_MODE === 'true'; if (isCli) { return 'cli'; } // Enhanced STDIO detection (using existing logic) if (isStdioTransport) { return 'stdio'; } // SSE/HTTP/WebSocket detection based on environment or args const args = getProcessArgs(); const isSSE = args.includes('--sse') || process.env.MCP_TRANSPORT === 'sse'; if (isSSE) { return 'sse'; } const isWebSocket = process.env.MCP_TRANSPORT === 'websocket'; if (isWebSocket) { return 'websocket'; } const isHTTP = process.env.MCP_TRANSPORT === 'http'; if (isHTTP) { return 'http'; } // Default fallback (preserving existing behavior) return 'sse'; } // Helper function to parse command line arguments (avoiding global scope pollution) function getProcessArgs(): string[] { return process.argv.slice(2); } // --- Calculate paths --- const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Transport-aware log file path // For CLI: use current working directory with session log name // For other transports: use package directory with server log name const transportType = detectTransportType(); const logFilePath = transportType === 'cli' ? path.join(process.cwd(), 'vibe-session.log') : path.resolve(__dirname, '../server.log'); // --- Create streams with graceful shutdown support --- // Store references to destinations for cleanup // For CLI: overwrite log file each session (append: false) // For other transports: append to existing log file (default behavior) const fileDestination = pino.destination({ dest: logFilePath, append: transportType !== 'cli' // CLI overwrites, others append }); const consoleStream = (isDevelopment && !isStdioTransport) ? process.stdout : process.stderr; // Log to file and also to the original console stream // In interactive mode, keep file logging at info level but suppress console output const streams = [ { level: effectiveLogLevel, stream: fileDestination }, // Always use stderr when stdio transport is detected to avoid interfering with MCP JSON-RPC protocol // In development, only use stdout if NOT using stdio transport // In interactive mode, suppress most console output by setting level to 'error' { level: isInteractiveMode ? 'error' : effectiveLogLevel, stream: consoleStream } ]; // Configure the logger const configuredLogger = pino( { level: effectiveLogLevel, // Set level here for filtering before transport/multistream // --- Add Redaction --- redact: { paths: [ 'apiKey', // Redact any top-level apiKey '*.apiKey', // Redact apiKey in any nested object 'receivedConfig.apiKey', // Specifically target the observed log structure 'config.apiKey', // Common config pattern 'openRouterConfig.apiKey', // Specific object name from index.ts 'env.OPENROUTER_API_KEY', // If env vars are logged directly 'env.PERPLEXITY_API_KEY' // Handle other potential keys // Add other sensitive keys if necessary, e.g., 'headers.Authorization' ], censor: '[REDACTED]', // Replace sensitive value with this string }, // --- End Redaction --- // Transport is applied *after* multistream, only affects console output here // Only use pretty printing in development AND when not using stdio transport transport: (isDevelopment && !isStdioTransport) ? { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', ignore: 'pid,hostname', // Pretty print options }, } : undefined, // Use default JSON transport for console when not in development or using stdio }, pino.multistream(streams) // Use multistream for output destinations ); // --- Graceful shutdown handling --- let shutdownInProgress = false; let loggerDestroyed = false; /** * Create a resilient logger wrapper that handles post-shutdown logging gracefully */ function createResilientLogger(baseLogger: Logger) { return new Proxy(baseLogger, { get(target, prop) { // If logger is destroyed and this is a logging method, use console instead if (loggerDestroyed && typeof prop === 'string' && ['debug', 'info', 'warn', 'error', 'fatal', 'trace'].includes(prop)) { return function(obj: unknown, msg?: string) { try { // Format the log message for console output if (typeof obj === 'string') { console.log(`[${prop.toUpperCase()}] ${obj}`); } else if (msg) { console.log(`[${prop.toUpperCase()}] ${msg}`, obj); } else { console.log(`[${prop.toUpperCase()}]`, obj); } } catch { // Silently ignore console errors } }; } // For non-logging methods or when logger is not destroyed, use original return (target as unknown as Record<string | symbol, unknown>)[prop]; } }); } /** * Gracefully shutdown logger streams to prevent sonic-boom crashes */ export function shutdownLogger(): Promise<void> { if (shutdownInProgress) { return Promise.resolve(); } shutdownInProgress = true; return new Promise((resolve) => { try { // Log shutdown initiation configuredLogger.info('Initiating logger shutdown'); // Handle SonicBoom destination gracefully if (fileDestination) { // Check if the destination is ready before attempting operations const isReady = (fileDestination as { ready?: boolean }).ready !== false; if (isReady) { // Try to flush synchronously only if ready try { if (typeof fileDestination.flushSync === 'function') { fileDestination.flushSync(); } } catch (flushError) { // Ignore flush errors during shutdown - the stream might not be ready console.warn('Warning: Could not flush logger during shutdown:', (flushError as Error).message); } } // Always try to end the stream gracefully try { if (typeof fileDestination.end === 'function') { fileDestination.end(); } } catch (endError) { console.warn('Warning: Could not end logger stream during shutdown:', (endError as Error).message); } } // Mark logger as destroyed to enable fallback behavior loggerDestroyed = true; // Give a small delay to ensure all writes are flushed setTimeout(() => { resolve(); }, 150); // Slightly longer delay to ensure cleanup } catch (error) { // Don't use logger here as it might be in a bad state console.error('Error during logger shutdown:', error); loggerDestroyed = true; resolve(); } }); } // Track registered shutdown callbacks const shutdownCallbacks: Array<() => Promise<void> | void> = []; /** * Register a callback to be called during graceful shutdown */ export function registerShutdownCallback(callback: () => Promise<void> | void): void { shutdownCallbacks.push(callback); } /** * Execute all registered shutdown callbacks */ async function executeShutdownCallbacks(): Promise<void> { for (const callback of shutdownCallbacks) { try { await callback(); } catch (error) { console.error('Error in shutdown callback:', error); } } } /** * Reset logger state for testing purposes * WARNING: This should only be used in test environments */ export function resetLoggerForTesting(): void { if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) { console.warn('resetLoggerForTesting() should only be used in test environments'); return; } shutdownInProgress = false; loggerDestroyed = false; } /** * Setup process exit handlers for graceful logger shutdown */ function setupShutdownHandlers(): void { let shutdownInitiated = false; const handleShutdown = async (signal: string) => { if (shutdownInitiated) { console.log(`\nForced shutdown on second ${signal}`); process.exit(1); } shutdownInitiated = true; try { console.log(`\nReceived ${signal}, shutting down gracefully...`); // Execute registered shutdown callbacks first (e.g., server cleanup) await executeShutdownCallbacks(); // Then shutdown logger await shutdownLogger(); console.log('Graceful shutdown completed'); process.exit(0); } catch (error) { console.error('Error during graceful shutdown:', error); process.exit(1); } }; // Handle various termination signals process.on('SIGINT', () => handleShutdown('SIGINT')); process.on('SIGTERM', () => handleShutdown('SIGTERM')); process.on('SIGQUIT', () => handleShutdown('SIGQUIT')); // Handle uncaught exceptions and unhandled rejections process.on('uncaughtException', async (error) => { console.error('Uncaught Exception:', error); try { // Try to execute shutdown callbacks and logger shutdown await executeShutdownCallbacks(); await shutdownLogger(); } catch (shutdownError) { console.error('Error during emergency shutdown:', shutdownError); } process.exit(1); }); process.on('unhandledRejection', async (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); try { // Try to execute shutdown callbacks and logger shutdown await executeShutdownCallbacks(); await shutdownLogger(); } catch (shutdownError) { console.error('Error during emergency shutdown:', shutdownError); } process.exit(1); }); } // Setup shutdown handlers when this module is imported setupShutdownHandlers(); // Export the resilient logger wrapper const resilientLogger = createResilientLogger(configuredLogger); export default resilientLogger;

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/freshtechbro/vibe-coder-mcp'

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