Skip to main content
Glama
http-logger.js11.8 kB
/** * HTTP Logger - A reusable HTTP logging utility for MCP servers * * Features: * - Request/response logging with correlation IDs * - Comprehensive diagnostic information * - Configurable debug levels * - Header sanitization for security * - Connection and TLS error details * - Performance timing * - Structured JSON output */ class HttpLogger { constructor(options = {}) { this.debugLogging = options.debug ?? (process.env.MCP_HTTP_DEBUG === 'true'); this.logLevel = options.logLevel || 'debug'; this.requestCounter = 0; this.serviceName = options.serviceName || 'mcp-server'; } /** * Generate unique correlation ID for request tracking */ generateCorrelationId() { return `req_${Date.now()}_${++this.requestCounter}`; } /** * Log HTTP request details */ logRequest(config, correlationId) { if (!this.debugLogging) { return; } const logData = { type: 'HTTP_REQUEST', service: this.serviceName, correlationId, timestamp: new Date().toISOString(), request: { method: config.method?.toUpperCase() || 'GET', url: config.url, baseURL: config.baseURL, fullUrl: this.buildFullUrl(config), headers: this.sanitizeHeaders(config.headers || {}), params: config.params, timeout: config.timeout, httpsAgent: config.httpsAgent ? 'configured' : 'default', maxRedirects: config.maxRedirects, validateStatus: typeof config.validateStatus === 'function' ? 'custom' : 'default' } }; this.log('debug', JSON.stringify(logData, null, 2)); } /** * Log HTTP response details */ logResponse(response, correlationId, startTime) { if (!this.debugLogging) { return; } const duration = Date.now() - startTime; const logData = { type: 'HTTP_RESPONSE', service: this.serviceName, correlationId, timestamp: new Date().toISOString(), response: { status: response.status, statusText: response.statusText, headers: this.sanitizeHeaders(response.headers || {}), duration: `${duration}ms`, dataSize: this.getDataSize(response.data), url: response.config?.url, method: response.config?.method?.toUpperCase(), redirected: response.request?._redirectCount > 0, redirectCount: response.request?._redirectCount || 0 }, performance: { totalTime: duration, dnsLookup: this.extractTimingInfo(response, 'lookup'), tcpConnection: this.extractTimingInfo(response, 'connect'), tlsHandshake: this.extractTimingInfo(response, 'secureConnect'), serverProcessing: this.extractTimingInfo(response, 'response') } }; this.log('debug', JSON.stringify(logData, null, 2)); } /** * Log HTTP errors with comprehensive diagnostic information */ logError(error, correlationId, startTime) { if (!this.debugLogging) { return; } const duration = startTime ? Date.now() - startTime : null; const logData = { type: 'HTTP_ERROR', service: this.serviceName, correlationId, timestamp: new Date().toISOString(), error: { message: error.message, code: error.code, errno: error.errno, syscall: error.syscall, status: error.response?.status, statusText: error.response?.statusText, headers: error.response?.headers ? this.sanitizeHeaders(error.response.headers) : undefined, data: error.response?.data ? this.truncateData(error.response.data) : undefined }, request: { url: error.config?.url, method: error.config?.method?.toUpperCase(), timeout: error.config?.timeout, duration: duration ? `${duration}ms` : 'unknown' }, connectionInfo: this.getConnectionInfo(error), troubleshooting: this.getTroubleshootingInfo(error) }; this.log('error', JSON.stringify(logData, null, 2)); } /** * Sanitize headers to remove sensitive information */ sanitizeHeaders(headers) { const sanitized = { ...headers }; const sensitivePatterns = [ 'token', 'authorization', 'cookie', 'auth', 'key', 'secret', 'password' ]; Object.keys(sanitized).forEach(key => { const lowerKey = key.toLowerCase(); if (sensitivePatterns.some(pattern => lowerKey.includes(pattern))) { sanitized[key] = '[REDACTED]'; } }); return sanitized; } /** * Get human-readable data size */ getDataSize(data) { if (!data) { return '0 bytes'; } try { const size = typeof data === 'string' ? data.length : JSON.stringify(data).length; if (size < 1024) { return `${size} bytes`; } if (size < 1024 * 1024) { return `${(size / 1024).toFixed(2)} KB`; } return `${(size / (1024 * 1024)).toFixed(2)} MB`; } catch { return 'unknown'; } } /** * Extract connection and network diagnostic information */ getConnectionInfo(error) { const info = {}; if (error.code) { info.errorCode = error.code; } if (error.address) { info.address = error.address; } if (error.port) { info.port = error.port; } if (error.syscall) { info.syscall = error.syscall; } if (error.errno) { info.errno = error.errno; } // DNS resolution errors if (error.code === 'ENOTFOUND') { info.dnsResolution = 'failed'; } if (error.code === 'EAI_AGAIN') { info.dnsResolution = 'temporary_failure'; } // Connection errors if (error.code === 'ECONNREFUSED') { info.connection = 'refused'; } if (error.code === 'ECONNRESET') { info.connection = 'reset'; } if (error.code === 'ETIMEDOUT') { info.connection = 'timeout'; } if (error.code === 'ECONNABORTED') { info.connection = 'aborted'; } // TLS/SSL certificate information if (error.code === 'CERT_HAS_EXPIRED') { info.tlsError = 'certificate_expired'; } if (error.code === 'SELF_SIGNED_CERT_IN_CHAIN') { info.tlsError = 'self_signed_certificate'; } if (error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') { info.tlsError = 'certificate_verification_failed'; } if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') { info.tlsError = 'self_signed_root_certificate'; } return Object.keys(info).length > 0 ? info : undefined; } /** * Get troubleshooting suggestions based on error type */ getTroubleshootingInfo(error) { const suggestions = []; switch (error.code) { case 'ENOTFOUND': suggestions.push('Check if the hostname is correct'); suggestions.push('Verify DNS resolution is working'); break; case 'ECONNREFUSED': suggestions.push('Check if the server is running'); suggestions.push('Verify the port number is correct'); suggestions.push('Check firewall rules'); break; case 'ETIMEDOUT': suggestions.push('Check network connectivity'); suggestions.push('Consider increasing timeout value'); suggestions.push('Verify server is responding'); break; case 'CERT_HAS_EXPIRED': suggestions.push('Update server certificate'); suggestions.push('Consider using httpsAgent with rejectUnauthorized: false for testing'); break; case 'SELF_SIGNED_CERT_IN_CHAIN': suggestions.push('Add certificate to trusted store'); suggestions.push('Use httpsAgent with custom CA for self-signed certificates'); break; } if (error.response?.status === 401) { suggestions.push('Check authentication credentials'); suggestions.push('Verify API token is valid'); } if (error.response?.status === 403) { suggestions.push('Check user permissions'); suggestions.push('Verify API access is enabled'); } if (error.response?.status >= 500) { suggestions.push('Server error - check server logs'); suggestions.push('Consider retry with exponential backoff'); } return suggestions.length > 0 ? suggestions : undefined; } /** * Build full URL from axios config */ buildFullUrl(config) { try { const baseURL = config.baseURL || ''; const url = config.url || ''; const fullUrl = baseURL + url; if (config.params) { const params = new URLSearchParams(config.params); return `${fullUrl}?${params.toString()}`; } return fullUrl; } catch { return config.url || 'unknown'; } } /** * Extract timing information from response */ extractTimingInfo(response, phase) { try { return response.request?.connection?.[`${phase}Time`] || 'unavailable'; } catch { return 'unavailable'; } } /** * Truncate large response data for logging */ truncateData(data, maxLength = 1000) { try { const str = typeof data === 'string' ? data : JSON.stringify(data); return str.length > maxLength ? str.substring(0, maxLength) + '...[truncated]' : str; } catch { return '[unable to serialize]'; } } /** * Generic log method that can be extended for different logging backends */ log(level, message) { switch (level) { case 'debug': console.debug(message); break; case 'info': console.info(message); break; case 'warn': console.warn(message); break; case 'error': console.error(message); break; default: console.log(message); } } /** * Create axios instance with HTTP logging interceptors */ createAxiosInstance(baseConfig = {}) { const axios = require('axios'); const instance = axios.create(baseConfig); // Request interceptor instance.interceptors.request.use( (config) => { const correlationId = this.generateCorrelationId(); const startTime = Date.now(); config.metadata = { correlationId, startTime }; this.logRequest(config, correlationId); return config; }, (error) => { this.log('error', `Request interceptor error: ${error.message}`); return Promise.reject(error); } ); // Response interceptor instance.interceptors.response.use( (response) => { const { correlationId, startTime } = response.config.metadata || {}; this.logResponse(response, correlationId, startTime); return response; }, (error) => { const { correlationId, startTime } = error.config?.metadata || {}; this.logError(error, correlationId, startTime); return Promise.reject(error); } ); return instance; } /** * Wrap existing axios instance with logging */ wrapAxiosInstance(axiosInstance) { // Request interceptor axiosInstance.interceptors.request.use( (config) => { const correlationId = this.generateCorrelationId(); const startTime = Date.now(); config.metadata = { correlationId, startTime }; this.logRequest(config, correlationId); return config; }, (error) => { this.log('error', `Request interceptor error: ${error.message}`); return Promise.reject(error); } ); // Response interceptor axiosInstance.interceptors.response.use( (response) => { const { correlationId, startTime } = response.config.metadata || {}; this.logResponse(response, correlationId, startTime); return response; }, (error) => { const { correlationId, startTime } = error.config?.metadata || {}; this.logError(error, correlationId, startTime); return Promise.reject(error); } ); return axiosInstance; } } module.exports = { HttpLogger };

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/vyb1ng/plex-mcp'

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