Skip to main content
Glama
debug.ts7.58 kB
/** * Debug Utility for MCP Server * * This module provides debug logging that only outputs when DEBUG mode is enabled. * This prevents console.log from interfering with MCP STDIO communication. */ import type { DebugInfo } from "@/types/index.js"; import { config } from "@/config/Config.js"; // Log levels export type LogLevel = "debug" | "info" | "warn" | "error"; // Logger interface export interface Logger { log(...args: unknown[]): void; info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void; } // Enhanced logger with structured logging export interface StructuredLogger extends Logger { logStructured(info: DebugInfo): void; child(context: Record<string, unknown>): StructuredLogger; } import { ConfigHelpers } from "@/config/Config.js"; // Check if debug mode is enabled const isDebugMode = (): boolean => ConfigHelpers.shouldDebug(); // Get current timestamp const getTimestamp = (): string => new Date().toISOString(); // Format log message with timestamp and level const formatMessage = (level: LogLevel, args: unknown[]): string => { const timestamp = getTimestamp(); const prefix = `[${timestamp}] [${level.toUpperCase()}]`; return `${prefix} ${args .map((arg) => (typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg))) .join(" ")}`; }; // Handle circular references in objects const safeStringify = (obj: unknown): string => { try { return JSON.stringify( obj, (key, value) => { if (value instanceof Error) { return { name: value.name, message: value.message, stack: value.stack, }; } return value; }, 2, ); } catch (_error) { return "[Object with circular reference]"; } }; /** * Debug logger that only outputs in debug mode */ export const debug: Logger = { log: (...args: unknown[]): void => { if (isDebugMode()) { console.error(formatMessage("debug", args)); // Use stderr for debug to avoid STDIO interference } }, info: (...args: unknown[]): void => { if (isDebugMode()) { console.error(formatMessage("info", args)); } }, warn: (...args: unknown[]): void => { if (isDebugMode()) { console.error(formatMessage("warn", args)); } }, error: (...args: unknown[]): void => { if (isDebugMode()) { console.error(formatMessage("error", args)); } }, }; /** * Silent logger for production use */ export const silent: Logger = { log: (): void => {}, warn: (): void => {}, error: (): void => {}, info: (): void => {}, }; /** * Enhanced structured logger */ class StructuredLoggerImpl implements StructuredLogger { private context: Record<string, unknown>; private enabled: boolean; constructor(context: Record<string, unknown> = {}, enabled = isDebugMode()) { this.context = context; this.enabled = enabled; } private output(level: LogLevel, args: unknown[]): void { if (!this.enabled) return; const debugInfo: DebugInfo = { timestamp: Date.now(), level, message: args.map((arg) => (typeof arg === "object" ? safeStringify(arg) : String(arg))).join(" "), ...(Object.keys(this.context).length > 0 && { context: this.context }), }; console.error(safeStringify(debugInfo)); } log(...args: unknown[]): void { this.output("debug", args); } info(...args: unknown[]): void { this.output("info", args); } warn(...args: unknown[]): void { this.output("warn", args); } error(...args: unknown[]): void { this.output("error", args); } logStructured(info: DebugInfo): void { if (!this.enabled) return; const enhancedInfo: DebugInfo = { ...info, context: { ...this.context, ...info.context }, }; console.error(safeStringify(enhancedInfo)); } child(context: Record<string, unknown>): StructuredLogger { return new StructuredLoggerImpl({ ...this.context, ...context }, this.enabled); } } /** * Create a structured logger instance */ export const createStructuredLogger = (context: Record<string, unknown> = {}): StructuredLogger => { return new StructuredLoggerImpl(context); }; /** * Default logger instance */ export const logger: Logger = debug; /** * Create a logger with context */ export const createLogger = (context: Record<string, unknown> = {}): StructuredLogger => { return createStructuredLogger(context); }; /** * Performance measurement utility */ export interface PerformanceTimer { end(): number; endWithLog(message?: string): number; } export const startTimer = (label?: string): PerformanceTimer => { const start = Date.now(); return { end(): number { return Date.now() - start; }, endWithLog(message = "Operation"): number { const duration = Date.now() - start; debug.info(`${message} completed in ${duration}ms${label ? ` [${label}]` : ""}`); return duration; }, }; }; /** * Log error with stack trace */ export const logError = (error: Error | string, context?: Record<string, unknown>): void => { if (typeof error === "string") { debug.error(error, context); } else { debug.error(`${error.name}: ${error.message}`, { stack: error.stack, ...context, }); } }; /** * Conditional logging */ export const logIf = (condition: boolean, level: LogLevel = "debug") => { if (!condition) return silent; const loggers = { log: debug.log, info: debug.info, warn: debug.warn, error: debug.error, }; return loggers[level as keyof typeof loggers] || debug.log; }; /** * Type-safe environment variable getter with security logging */ export const getEnvVar = (key: string, defaultValue?: string): string | undefined => { const value = process.env[key]; if (value === undefined && defaultValue !== undefined) { // Don't log sensitive environment variable names in production const isSecretVar = /password|secret|token|key|auth/i.test(key); if (!isSecretVar || !config().app.isProduction) { debug.warn(`Environment variable ${key} not found, using default`); } return defaultValue; } return value; }; /** * Validate required environment variables with secure error messages */ export const validateEnvVars = (required: string[]): void => { const missing = required.filter((key) => !process.env[key]); if (missing.length > 0) { // In production, don't expose which specific env vars are missing const errorMessage = config().app.isProduction ? `Missing ${missing.length} required environment variable(s)` : `Missing required environment variables: ${missing.join(", ")}`; const error = new Error(errorMessage); logError(error); throw error; } }; /** * Sanitize environment variable for logging */ export const sanitizeEnvValue = (key: string, value: string): string => { const sensitiveKeys = /password|secret|token|key|auth|credential/i; if (sensitiveKeys.test(key)) { if (!value || value.length === 0) { return "[EMPTY]"; } return `[REDACTED:${value.length}chars]`; } return value; }; /** * Get sanitized environment summary for debugging */ export const getEnvSummary = (keys: string[]): Record<string, string> => { const summary: Record<string, string> = {}; for (const key of keys) { const value = process.env[key]; if (value !== undefined) { summary[key] = sanitizeEnvValue(key, value); } else { summary[key] = "[NOT_SET]"; } } return summary; };

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/docdyhr/mcp-wordpress'

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