Skip to main content
Glama

MCPMan

by semistrict
logging.ts6.69 kB
import { closeSync, openSync, writeSync } from "node:fs"; import { homedir } from "node:os"; import { dirname } from "node:path"; import { getConfigDir } from "../config/loader.js"; let logFd: number | null = null; let traceFd: number | null = null; let originalConsole: { log: typeof console.log; error: typeof console.error; warn: typeof console.warn; info: typeof console.info; } | null = null; export function redirectConsole(): void { if (originalConsole) { // Already redirected return; } // Get log path using config directory const configDir = getConfigDir(); const logPath = `${configDir}/mcpman.log`; // Ensure directory exists try { const logDir = dirname(logPath); require("node:fs").mkdirSync(logDir, { recursive: true }); } catch (error) { console.error(`Failed to create log directory: ${error}`); return; } // Open log file descriptor once for synchronous writes try { logFd = openSync(logPath, "a"); } catch (error) { console.error(`Failed to open log file: ${error}`); return; } // Store original console methods originalConsole = { log: console.log, error: console.error, warn: console.warn, info: console.info, }; // Helper function to write only to log file const writeToLog = (level: string, args: unknown[]) => { const timestamp = new Date().toISOString(); const message = args .map((arg) => { if (typeof arg === "string") { return arg; } if (arg instanceof Error) { let errorStr = `${arg.name}: ${arg.message}`; if (arg.stack) { errorStr += `\n${arg.stack}`; } if (arg.cause) { errorStr += `\nCaused by: ${arg.cause instanceof Error ? arg.cause.message : String(arg.cause)}`; } return errorStr; } return JSON.stringify(arg); }) .join(" "); if (logFd !== null) { // Use synchronous write to ensure logs are written even if process crashes try { writeSync(logFd, `[${timestamp}] ${level}: ${message}\n`); } catch (_error) { // Can't log this error since console is redirected } } }; // Override console methods to only log to file console.log = (...args: unknown[]) => { writeToLog("LOG", args); }; console.error = (...args: unknown[]) => { writeToLog("ERROR", args); }; console.warn = (...args: unknown[]) => { writeToLog("WARN", args); }; console.info = (...args: unknown[]) => { writeToLog("INFO", args); }; } export function TRACE(strings: TemplateStringsArray | string, ...values: unknown[]): void { if (!process.env.MCPMAN_TRACE) { return; } if (traceFd === null) { const tracePath = `${homedir()}/.mcpman/trace-${process.pid}.log`; // Ensure directory exists try { const traceDir = dirname(tracePath); require("node:fs").mkdirSync(traceDir, { recursive: true }); } catch (error) { console.error(`Failed to create trace directory: ${error}`); return; } try { traceFd = openSync(tracePath, "w"); // Print trace file path to stderr so it's visible process.stderr.write(`TRACE: Writing to ${tracePath}\n`); } catch (error) { console.error(`Failed to open trace file: ${error}`); return; } } // Get caller file and line number const stack = new Error().stack; let location = "unknown"; if (stack) { const lines = stack.split("\n"); // Skip the first line (Error:) and second line (this function) const callerLine = lines[2]; if (callerLine) { // Extract file:line from something like "at functionName (/path/to/file.ts:123:45)" const match = callerLine.match(/\(([^)]+)\)$/) || callerLine.match(/at (.+)$/); if (match?.[1]) { const fullPath = match[1]; // Extract just filename:line from full path const fileMatch = fullPath.match(/([^/]+):(\d+):\d+$/); if (fileMatch) { location = `${fileMatch[1]}:${fileMatch[2]}`; } } } } const timestamp = new Date().toISOString(); // Handle tagged template literal or regular string let message: string; if (typeof strings === "string") { // Called as TRACE("string") message = strings; } else { // Called as TRACE`template ${value}` message = strings.reduce((acc, str, i) => { const value = values[i]; const valueStr = value instanceof Error ? `${value.name}: ${value.message}` : typeof value === "string" ? value : JSON.stringify(value); return acc + str + (i < values.length ? valueStr : ""); }, ""); } if (traceFd !== null) { try { writeSync(traceFd, `[${timestamp}] TRACE [${location}]: ${message}\n`); // Write stack traces for any errors in values for (const value of values) { if (value instanceof Error && value.stack) { writeSync(traceFd, `[${timestamp}] TRACE [${location}]: Stack trace:\n`); const stackLines = value.stack.split("\n"); for (const line of stackLines) { writeSync(traceFd, `[${timestamp}] TRACE [${location}]: ${line}\n`); } if (value.cause) { writeSync( traceFd, `[${timestamp}] TRACE [${location}]: Caused by: ${value.cause instanceof Error ? value.cause.message : String(value.cause)}\n` ); if (value.cause instanceof Error && value.cause.stack) { const causeStackLines = value.cause.stack.split("\n"); for (const line of causeStackLines) { writeSync(traceFd, `[${timestamp}] TRACE [${location}]: ${line}\n`); } } } } } } catch (_error) { // Can't log this error since console is redirected } } } // Cleanup function to restore original console export function restoreConsole(): void { if (originalConsole) { console.log = originalConsole.log; console.error = originalConsole.error; console.warn = originalConsole.warn; console.info = originalConsole.info; originalConsole = null; } if (logFd !== null) { try { closeSync(logFd); } catch (_error) { // Ignore errors when closing } logFd = null; } if (traceFd !== null) { try { closeSync(traceFd); } catch (_error) { // Ignore errors when closing } traceFd = null; } } // Cleanup on process exit process.on("exit", restoreConsole); process.on("SIGINT", restoreConsole); process.on("SIGTERM", restoreConsole);

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/semistrict/mcpman'

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