Skip to main content
Glama
logger.ts3.71 kB
/** * Structured JSON Logger with Secret Masking. * * - Outputs JSON for easy parsing * - Masks API keys and secrets automatically * - Includes request IDs for tracing */ import { config } from '../config.js'; type LogLevel = 'debug' | 'info' | 'warn' | 'error'; interface LogEntry { timestamp: string; level: LogLevel; message: string; request_id?: string; [key: string]: unknown; } /** * Patterns that look like API keys or secrets. * These will be masked in log output. */ const SECRET_PATTERNS = [ // Long alphanumeric strings (likely API keys) /\b[a-zA-Z0-9]{32,}\b/g, // Patterns that look like secrets /(?:api[_-]?key|secret|password|token)[\s:="']+[^\s"']+/gi, ]; /** * Mask sensitive data in a value. */ function maskSecrets(value: unknown): unknown { if (typeof value === 'string') { let masked = value; for (const pattern of SECRET_PATTERNS) { masked = masked.replace(pattern, '[REDACTED]'); } return masked; } if (Array.isArray(value)) { return value.map(maskSecrets); } if (value && typeof value === 'object') { const maskedObj: Record<string, unknown> = {}; for (const [key, val] of Object.entries(value)) { // Always mask keys that look like secrets const lowerKey = key.toLowerCase(); if ( lowerKey.includes('secret') || lowerKey.includes('password') || lowerKey.includes('apikey') || lowerKey.includes('api_key') || lowerKey.includes('token') ) { maskedObj[key] = '[REDACTED]'; } else { maskedObj[key] = maskSecrets(val); } } return maskedObj; } return value; } /** * Log level priority for filtering. */ const LOG_LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3, }; /** * Should this level be logged? */ function shouldLog(level: LogLevel): boolean { return LOG_LEVELS[level] >= LOG_LEVELS[config.logLevel]; } /** * Generate a unique request ID. */ export function generateRequestId(): string { return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; } /** * Current request context (for tracing). */ let currentRequestId: string | undefined; export function setRequestId(id: string): void { currentRequestId = id; } export function clearRequestId(): void { currentRequestId = undefined; } /** * Core logging function. */ function log( level: LogLevel, message: string, data?: Record<string, unknown>, ): void { if (!shouldLog(level)) return; const entry: LogEntry = { timestamp: new Date().toISOString(), level, message, }; if (currentRequestId) { entry.request_id = currentRequestId; } // Add and mask additional data if (data) { const masked = maskSecrets(data) as Record<string, unknown>; Object.assign(entry, masked); } // Output to stderr (MCP servers use stdout for protocol) console.error(JSON.stringify(entry)); } /** * Logger instance with convenience methods. */ export const logger = { debug: (message: string, data?: Record<string, unknown>) => log('debug', message, data), info: (message: string, data?: Record<string, unknown>) => log('info', message, data), warn: (message: string, data?: Record<string, unknown>) => log('warn', message, data), error: (message: string, data?: Record<string, unknown>) => log('error', message, data), /** * Log an error with stack trace. */ logError: (message: string, error: Error, data?: Record<string, unknown>) => { log('error', message, { ...data, error_name: error.name, error_message: error.message, error_stack: error.stack, }); }, };

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/dorukardahan/domain-search-mcp'

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