Skip to main content
Glama
log-discovery-tools.tsβ€’20.5 kB
/** * Log Discovery Tools * Enhanced tools for discovering and accessing log containers across all environments * Especially important for Production where container names may vary */ import ResponseBuilder from '../response-builder'; import OutputLogger from '../output-logger'; import ProjectTools from './project-tools'; import StorageTools from './storage-tools'; /** * Log container pattern definition */ interface LogContainerPattern { pattern: RegExp; type: string; description: string; } /** * Container match result */ interface ContainerMatch { type: string; description: string; } /** * Log container info */ interface LogContainerInfo { name: string; type: string; description: string; } /** * Environment discovery result */ interface EnvironmentDiscoveryResult { accessible: boolean; totalContainers?: number; logContainers?: LogContainerInfo[]; otherContainers?: string[]; error?: string; } /** * Discovery results (all environments) */ interface DiscoveryResults { [environment: string]: EnvironmentDiscoveryResult; } /** * Discovery arguments */ interface DiscoveryArgs { [key: string]: any; } /** * Response with structured content */ interface StructuredResponse { structuredContent?: { data?: { containers?: string[]; }; }; data?: { containers?: string[]; }; error?: string; content?: Array<{ text?: string }>; result?: { content?: Array<{ text?: string }>; }; } class LogDiscoveryTools { // Known log container patterns static LOG_CONTAINER_PATTERNS: LogContainerPattern[] = [ // Standard DXP containers { pattern: /^appservicelogs$/, type: 'application', description: 'Application logs' }, { pattern: /^webservicelogs$/, type: 'web', description: 'Web server logs' }, { pattern: /^cloudflarelogpush$/, type: 'cloudflare', description: 'Cloudflare logs (beta)' }, // App Service Insights containers { pattern: /^insights-logs-appserviceconsolelogs$/, type: 'application', description: 'App Service console logs' }, { pattern: /^insights-logs-appservicehttplogs$/, type: 'web', description: 'App Service HTTP logs' }, { pattern: /^insights-logs-/, type: 'insights', description: 'Application Insights logs' }, // Alternative Azure containers (commonly found in Production) { pattern: /^azure-application-logs$/, type: 'application', description: 'Azure application logs' }, { pattern: /^azure-web-logs$/, type: 'web', description: 'Azure web server logs' }, { pattern: /^azure-logs-/, type: 'azure', description: 'Azure logs' }, // Application Insights containers { pattern: /applicationinsights/, type: 'appinsights', description: 'Application Insights data' }, { pattern: /appinsights/, type: 'appinsights', description: 'Application Insights data' }, // Enhanced generic log patterns { pattern: /logs?$/, type: 'generic', description: 'Generic log container' }, { pattern: /log-/, type: 'generic', description: 'Generic log container' }, { pattern: /-logs?$/, type: 'generic', description: 'Log container (suffix)' }, { pattern: /diagnostic.*logs?/i, type: 'diagnostic', description: 'Diagnostic logs' }, { pattern: /inte-.*logs?/i, type: 'integration', description: 'Integration environment logs' }, { pattern: /backup.*logs?/i, type: 'backup', description: 'Backup logs' }, { pattern: /^db-backups?-/i, type: 'database', description: 'Database backup container' } ]; /** * Discover all log containers across environments */ static async discoverLogContainers(args: DiscoveryArgs): Promise<any> { try { OutputLogger.info('πŸ” Discovering log containers across all environments...\n'); // Resolve project configuration const resolved = ProjectTools.resolveCredentials(args); if (!resolved.success || !resolved.credentials) { return ResponseBuilder.invalidParams('Missing required project configuration'); } const projectConfig = resolved.credentials; const projectName = resolved.project ? resolved.project.name : 'Unknown'; OutputLogger.info(`πŸ“‹ Project: ${projectName}`); OutputLogger.info(`πŸ”‘ Using API key: ${projectConfig.apiKey?.substring(0, 8)}...`); // Test each environment const environments = ['Production', 'Preproduction', 'Integration']; const discoveryResults: DiscoveryResults = {}; for (const env of environments) { OutputLogger.info(`\n🌍 Checking ${env} environment...`); try { const envArgs = { ...projectConfig, environment: env }; if (process.env.DEBUG === 'true') { console.error(`[DISCOVER_LOGS] Calling handleListStorageContainers for ${env}`); console.error(`[DISCOVER_LOGS] Args:`, { hasApiKey: !!envArgs.apiKey, hasApiSecret: !!envArgs.apiSecret, hasProjectId: !!envArgs.projectId, environment: envArgs.environment }); } // Get all containers for this environment const containersResult = await StorageTools.handleListStorageContainers(envArgs as any); if (process.env.DEBUG === 'true') { console.error(`[DISCOVER_LOGS] Result type:`, typeof containersResult); console.error(`[DISCOVER_LOGS] Result structure:`, Object.keys(containersResult || {})); } const containers = this.extractContainerList(containersResult); if (process.env.DEBUG === 'true') { console.error(`[DISCOVER_LOGS] Extracted ${containers.length} containers from result`); } if (!containers || containers.length === 0) { OutputLogger.info(` ⚠️ No containers accessible in ${env}`); discoveryResults[env] = { accessible: false, logContainers: [], otherContainers: [] }; continue; } // Identify log containers const logContainers: LogContainerInfo[] = []; const otherContainers: string[] = []; for (const container of containers) { const match = this.identifyLogContainer(container); if (match) { logContainers.push({ name: container, type: match.type, description: match.description }); } else { otherContainers.push(container); } } discoveryResults[env] = { accessible: true, totalContainers: containers.length, logContainers: logContainers, otherContainers: otherContainers }; // Display results for this environment OutputLogger.info(` βœ… Found ${containers.length} total containers`); if (logContainers.length > 0) { OutputLogger.info(` πŸ“Š Log containers (${logContainers.length}):`); for (const log of logContainers) { OutputLogger.info(` β€’ ${log.name} (${log.description})`); } } else { OutputLogger.info(` ⚠️ No log containers found!`); } if (otherContainers.length > 0) { OutputLogger.info(` πŸ“¦ ALL other containers (${otherContainers.length}):`); for (const container of otherContainers) { OutputLogger.info(` β€’ ${container}`); } } } catch (error: any) { if (process.env.DEBUG === 'true') { console.error(`[DISCOVER_LOGS] Full error for ${env}:`, error); console.error(`[DISCOVER_LOGS] Error stack:`, error.stack); } OutputLogger.error(` ❌ Error accessing ${env}: ${error.message}`); // Provide more context in the error let errorDetail = error.message; if (error.response) { errorDetail += ` (HTTP ${error.response.statusCode || error.response.status})`; } if (error.code) { errorDetail += ` [${error.code}]`; } discoveryResults[env] = { accessible: false, error: errorDetail }; } } // Generate diagnostic report return this.generateDiagnosticReport(discoveryResults, projectName); } catch (error: any) { return ResponseBuilder.error(`Log discovery failed: ${error.message}`); } } /** * Identify if a container is a log container */ static identifyLogContainer(containerName: string): ContainerMatch | null { for (const pattern of this.LOG_CONTAINER_PATTERNS) { if (pattern.pattern.test(containerName)) { return { type: pattern.type, description: pattern.description }; } } return null; } /** * Extract container list from response */ static extractContainerList(response: any): string[] { if (!response) { return []; } // DXP-86: First check for structured data (preferred method) if (typeof response === 'object' && response !== null) { const structuredResponse = response as StructuredResponse; // Check for structuredContent.data.containers (MCP protocol format) if (structuredResponse.structuredContent?.data?.containers) { if (process.env.DEBUG === 'true') { console.error('[DISCOVER_LOGS] Found containers in structuredContent.data.containers'); } return structuredResponse.structuredContent.data.containers; } // Check for direct data.containers format if (structuredResponse.data?.containers) { if (process.env.DEBUG === 'true') { console.error('[DISCOVER_LOGS] Found containers in data.containers'); } return structuredResponse.data.containers; } // Check for error response if (structuredResponse.error) { OutputLogger.info(`Error in container list response: ${structuredResponse.error}`); return []; } } // Fall back to text parsing for legacy formats let text = ''; if (typeof response === 'object' && response !== null) { const structuredResponse = response as StructuredResponse; if (structuredResponse.content && Array.isArray(structuredResponse.content) && structuredResponse.content[0]) { text = structuredResponse.content[0].text || ''; } else if (structuredResponse.result && structuredResponse.result.content && Array.isArray(structuredResponse.result.content)) { const content = structuredResponse.result.content[0]; if (content && content.text) { text = content.text; } } else { text = JSON.stringify(response); } } else if (typeof response === 'string') { text = response; } if (!text) { return []; } if (process.env.DEBUG === 'true') { console.error('[DISCOVER_LOGS] Falling back to text parsing'); } const containers: string[] = []; const lines = text.split('\n'); for (const line of lines) { if (!line.trim()) continue; // Look for numbered emoji format: "1. πŸ“¦ container-name" let match = line.match(/^\d+\.\s*πŸ“¦\s*(.+)$/); if (match) { containers.push(match[1].trim()); continue; } // Look for simple bullet format: "- container-name" or "β€’ container-name" match = line.match(/^[\s\-β€’]\s*([^\s\-β€’].+)$/); if (match && !match[1].includes('Storage Container') && !match[1].includes('---')) { const name = match[1].trim(); if (name && !name.includes('|') && !name.includes('Environment')) { containers.push(name); } continue; } // Look for markdown table format: "| container-name |" match = line.match(/\|\s*([^|]+)\s*\|/); if (match && !match[1].includes('Storage Container') && !match[1].includes('---')) { containers.push(match[1].trim()); continue; } // Try JSON parsing if it looks like container data if (line.includes('{') || line.includes('[')) { try { const data = JSON.parse(line); if (Array.isArray(data)) { data.forEach((item: any) => { const name = item.StorageContainer || item.Name || item.ContainerName; if (name) containers.push(name); }); } else if (data.StorageContainer || data.Name || data.ContainerName) { containers.push(data.StorageContainer || data.Name || data.ContainerName); } } catch (e) { // Not JSON, continue } } } return containers.filter(Boolean); } /** * Generate diagnostic report */ static generateDiagnosticReport(results: DiscoveryResults, projectName: string): any { let message = `# πŸ” Log Container Discovery Report\n\n`; message += `**Project**: ${projectName}\n`; message += `**Timestamp**: ${new Date().toISOString()}\n\n`; // Check for critical issues const prodResult = results['Production']; const hasProductionIssue = !prodResult?.accessible || prodResult?.logContainers?.length === 0; if (hasProductionIssue) { message += `## 🚨 CRITICAL ISSUE DETECTED\n\n`; if (!prodResult?.accessible) { message += `❌ **Cannot access Production containers**\n`; message += ` Error: ${prodResult?.error || 'Access denied'}\n\n`; message += `### Recommended Actions:\n`; message += `1. Verify API key has Production access\n`; message += `2. Contact Optimizely Support to enable Production logging\n`; message += `3. Check if Production environment exists for this project\n\n`; } else if (prodResult?.logContainers?.length === 0) { message += `⚠️ **No log containers found in Production**\n`; message += ` Found ${prodResult.totalContainers} containers, but none are log containers\n\n`; message += `### Possible Causes:\n`; message += `1. **Logging not enabled**: Contact Optimizely Support to enable Production logging\n`; message += `2. **Non-standard names**: Containers may have custom names\n`; message += `3. **Permission issue**: API key may lack log container access\n\n`; if (prodResult.otherContainers?.length && prodResult.otherContainers.length > 0) { message += `### ALL containers found in Production (${prodResult.otherContainers.length} total):\n`; for (const container of prodResult.otherContainers) { message += ` β€’ \`${container}\`\n`; } message += `\n`; message += `πŸ’‘ **Tip**: Any of these might contain logs with non-standard names.\n`; message += `Try: \`download_logs environment: "Production" containerName: "[container-name]"\`\n\n`; } } } // Environment summary message += `## πŸ“Š Environment Summary\n\n`; for (const [env, result] of Object.entries(results)) { const icon = result.accessible ? 'βœ…' : '❌'; const logCount = result.logContainers?.length || 0; message += `### ${icon} ${env}\n`; if (!result.accessible) { message += ` Status: **Not accessible**\n`; if (result.error) { message += ` Error: ${result.error}\n`; } } else { message += ` Status: **Accessible**\n`; message += ` Total containers: ${result.totalContainers}\n`; message += ` Log containers: ${logCount}\n`; if (logCount > 0 && result.logContainers) { message += ` Available logs:\n`; for (const log of result.logContainers) { message += ` β€’ \`${log.name}\` - ${log.description}\n`; } } } message += `\n`; } // Recommendations message += `## πŸ’‘ Recommendations\n\n`; if (hasProductionIssue) { message += `### For Production Log Access:\n`; message += `1. **Contact Optimizely Support** with this information:\n`; message += ` - Request to enable Production logging\n`; message += ` - Project ID: Include your project ID\n`; message += ` - Mention you need access to Application Insights logs\n`; message += ` - Request both console and HTTP logs\n\n`; message += `2. **Alternative Methods** while waiting:\n`; message += ` - Check DXP Management Portal for downloadable logs\n`; message += ` - Use Application Insights in Azure Portal (if accessible)\n`; message += ` - Request Kudu access from Optimizely Support\n\n`; } // Working examples from other environments const workingEnvs = Object.entries(results) .filter(([_env, r]) => r.accessible && r.logContainers && r.logContainers.length > 0) .map(([env, r]) => ({ env, containers: r.logContainers! })); if (workingEnvs.length > 0) { message += `### βœ… Working Log Access:\n`; for (const { env, containers } of workingEnvs) { message += `\n**${env}** - Use these commands:\n`; for (const container of containers) { message += `\`\`\`\n`; message += `download_logs environment: "${env}" containerName: "${container.name}"\n`; message += `\`\`\`\n`; } } } message += `\n## πŸ“ž Support Contact\n`; message += `If Production logs remain inaccessible:\n`; message += `β€’ **Optimizely Support**: support@optimizely.com\n`; message += `β€’ **Reference**: "Production Application Insights log access"\n`; message += `β€’ **Include**: This diagnostic report\n`; return ResponseBuilder.success(message); } } export default LogDiscoveryTools;

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