Skip to main content
Glama
webhook-logger.ts8.15 kB
/** * Webhook Logger * Delivery tracking and logging for webhooks * Part of Jaxon Digital Optimizely DXP MCP Server - DXP-136 Phase 2 */ /** * Log entry for webhook delivery */ export interface LogEntry { webhookId: string; url: string; status: 'success' | 'failed' | 'retrying' | 'error'; timestamp: string; attempts?: number; responseTime?: number; statusCode?: number; errors?: any[]; finalError?: string; attemptNumber?: number; retryDelay?: number; error?: string; context?: Record<string, any>; } /** * Delivery statistics */ export interface LogStats { total: number; success: number; failed: number; retrying: number; errors: number; avgResponseTime?: number; successRate?: number; } /** * Log filter options */ export interface LogFilterOptions { since?: string | Date; url?: string; } /** * Get recent logs options */ export interface GetRecentLogsOptions { limit?: number; status?: 'success' | 'failed' | 'retrying' | 'error'; url?: string; } /** * Webhook Logger Class * Tracks webhook delivery attempts and outcomes */ class WebhookLogger { static deliveryLog: LogEntry[] = []; static maxLogSize = 1000; // Keep last 1000 deliveries /** * Log successful webhook delivery * @param webhookId - Webhook ID * @param url - Webhook URL * @param details - Delivery details */ static logSuccess(webhookId: string, url: string, details: { attempts?: number; responseTime?: number; statusCode?: number } = {}): void { const logEntry: LogEntry = { webhookId, url: this.sanitizeUrl(url), status: 'success', timestamp: new Date().toISOString(), attempts: details.attempts || 1, responseTime: details.responseTime, statusCode: details.statusCode }; this.addToLog(logEntry); if (process.env.DEBUG === 'true') { console.error(`[WEBHOOK] ✅ Success: ${webhookId} → ${this.sanitizeUrl(url)} (${details.responseTime}ms, ${details.attempts} attempts)`); } } /** * Log webhook delivery failure * @param webhookId - Webhook ID * @param url - Webhook URL * @param details - Failure details */ static logFailure(webhookId: string, url: string, details: { attempts?: number; errors?: any[]; finalError?: string } = {}): void { const logEntry: LogEntry = { webhookId, url: this.sanitizeUrl(url), status: 'failed', timestamp: new Date().toISOString(), attempts: details.attempts, errors: details.errors, finalError: details.finalError }; this.addToLog(logEntry); console.error(`[WEBHOOK] ❌ Failed: ${webhookId} → ${this.sanitizeUrl(url)} after ${details.attempts} attempts: ${details.finalError}`); } /** * Log webhook retry * @param webhookId - Webhook ID * @param url - Webhook URL * @param attemptNumber - Current attempt number * @param retryDelay - Retry delay in ms * @param error - Error message */ static logRetry(webhookId: string, url: string, attemptNumber: number, retryDelay: number, error: string): void { const logEntry: LogEntry = { webhookId, url: this.sanitizeUrl(url), status: 'retrying', timestamp: new Date().toISOString(), attemptNumber, retryDelay, error }; this.addToLog(logEntry); if (process.env.DEBUG === 'true') { console.error(`[WEBHOOK] 🔄 Retry ${attemptNumber}: ${webhookId} → ${this.sanitizeUrl(url)} in ${retryDelay}ms (${error})`); } } /** * Log webhook error * @param webhookId - Webhook ID * @param url - Webhook URL * @param error - Error message * @param context - Additional context */ static logError(webhookId: string, url: string, error: string, context: Record<string, any> = {}): void { const logEntry: LogEntry = { webhookId, url: this.sanitizeUrl(url), status: 'error', timestamp: new Date().toISOString(), error, context }; this.addToLog(logEntry); console.error(`[WEBHOOK] ⚠️ Error: ${webhookId} → ${this.sanitizeUrl(url)}: ${error}`); } /** * Add entry to delivery log * @param logEntry - Log entry */ static addToLog(logEntry: LogEntry): void { this.deliveryLog.push(logEntry); // Trim log if it exceeds max size if (this.deliveryLog.length > this.maxLogSize) { this.deliveryLog = this.deliveryLog.slice(-this.maxLogSize); } } /** * Sanitize URL for logging (remove sensitive query params) * @param url - URL to sanitize * @returns Sanitized URL */ static sanitizeUrl(url: string): string { try { const parsed = new URL(url); // Remove query params that might contain secrets const sensitiveParams = ['token', 'key', 'secret', 'password', 'apikey', 'api_key']; for (const param of sensitiveParams) { if (parsed.searchParams.has(param)) { parsed.searchParams.set(param, '***'); } } return parsed.toString(); } catch (error) { return url; // Return original if parsing fails } } /** * Get delivery statistics * @param options - Filter options * @returns Statistics */ static getStats(options: LogFilterOptions = {}): LogStats { const { since, url } = options; let logs = this.deliveryLog; // Filter by time if (since) { const sinceTime = new Date(since).getTime(); logs = logs.filter(log => new Date(log.timestamp).getTime() >= sinceTime); } // Filter by URL if (url) { const sanitizedUrl = this.sanitizeUrl(url); logs = logs.filter(log => log.url === sanitizedUrl); } // Calculate stats const stats: LogStats = { total: logs.length, success: logs.filter(log => log.status === 'success').length, failed: logs.filter(log => log.status === 'failed').length, retrying: logs.filter(log => log.status === 'retrying').length, errors: logs.filter(log => log.status === 'error').length }; // Calculate average response time for successful deliveries const successfulLogs = logs.filter(log => log.status === 'success' && log.responseTime); if (successfulLogs.length > 0) { stats.avgResponseTime = Math.round( successfulLogs.reduce((sum, log) => sum + (log.responseTime || 0), 0) / successfulLogs.length ); } // Calculate success rate if (stats.total > 0) { stats.successRate = Math.round((stats.success / stats.total) * 100); } return stats; } /** * Get recent delivery log entries * @param options - Filter options * @returns Log entries */ static getRecentLogs(options: GetRecentLogsOptions = {}): LogEntry[] { const { limit = 100, status, url } = options; let logs = [...this.deliveryLog]; // Filter by status if (status) { logs = logs.filter(log => log.status === status); } // Filter by URL if (url) { const sanitizedUrl = this.sanitizeUrl(url); logs = logs.filter(log => log.url === sanitizedUrl); } // Sort by timestamp (most recent first) logs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // Apply limit return logs.slice(0, limit); } /** * Clear delivery log */ static clearLog(): void { this.deliveryLog = []; } } export default WebhookLogger;

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/JaxonDigital/optimizely-dxp-mcp'

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