UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

463 lines 17.6 kB
/** * Enhanced Console Capture * * Improves console log capture to provide detailed JavaScript error information * including stack traces, error context, and structured output. */ export class EnhancedConsoleCapture { logs = []; page; isCapturing = false; consoleHandler; pageErrorHandler; constructor(page) { this.page = page; } /** * Start capturing console logs with enhanced detail */ async startCapture() { if (this.isCapturing) return; this.isCapturing = true; this.logs = []; // Inject enhanced error capture script await this.injectEnhancedCapture(); // Set up Playwright console message handler this.consoleHandler = async (msg) => { await this.handleConsoleMessage(msg); }; this.page.on('console', this.consoleHandler); // Set up page error handler this.pageErrorHandler = (error) => { this.handlePageError(error); }; this.page.on('pageerror', this.pageErrorHandler); console.error('🎯 Enhanced console capture started'); } /** * Stop capturing console logs */ stopCapture() { if (!this.isCapturing) return; this.isCapturing = false; if (this.consoleHandler) { this.page.off('console', this.consoleHandler); } if (this.pageErrorHandler) { this.page.off('pageerror', this.pageErrorHandler); } console.error('🛑 Enhanced console capture stopped'); } /** * Get captured logs with optional filtering */ getLogs(options) { let filtered = [...this.logs]; // Filter by level if (options?.level && options.level !== 'all') { filtered = filtered.filter(log => log.level === options.level); } // Sort by timestamp (newest first) filtered.sort((a, b) => b.timestamp - a.timestamp); // Apply limit if (options?.limit) { filtered = filtered.slice(0, options.limit); } return filtered; } /** * Analyze console logs for patterns and issues */ analyze() { const summary = { totalLogs: this.logs.length, errorCount: 0, warningCount: 0, infoCount: 0, categories: {} }; const criticalErrors = []; const errorPatterns = new Map(); for (const log of this.logs) { // Count by level switch (log.level) { case 'error': summary.errorCount++; criticalErrors.push(log); break; case 'warning': summary.warningCount++; break; case 'info': case 'log': summary.infoCount++; break; } // Count by category if (log.category) { summary.categories[log.category] = (summary.categories[log.category] || 0) + 1; } // Detect patterns in errors if (log.level === 'error') { const pattern = this.extractErrorPattern(log.text); errorPatterns.set(pattern, (errorPatterns.get(pattern) || 0) + 1); } } // Generate recommendations const recommendations = this.generateRecommendations(summary, criticalErrors); // Extract top error patterns const patterns = Array.from(errorPatterns.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([pattern, count]) => `${pattern} (${count} occurrences)`); return { summary, criticalErrors: criticalErrors.slice(0, 10), // Top 10 critical errors recommendations, patterns }; } /** * Get formatted report of console logs */ getFormattedReport(options) { const format = options?.format || 'markdown'; const analysis = options?.includeAnalysis ? this.analyze() : null; switch (format) { case 'markdown': return this.formatAsMarkdown(analysis); case 'json': return JSON.stringify({ logs: this.logs, analysis }, null, 2); case 'text': return this.formatAsText(analysis); default: return this.formatAsMarkdown(analysis); } } /** * Inject enhanced error capture into the page */ async injectEnhancedCapture() { await this.page.evaluate(() => { // Store original console methods const originalConsole = { error: console.error, warn: console.warn, log: console.log, info: console.info, debug: console.debug }; // Enhanced console wrapper const enhancedConsole = (level, ...args) => { // Call original method originalConsole[level](...args); // Capture enhanced information const error = new Error(); const stack = error.stack || ''; const location = stack.split('\n')[3] || ''; // Skip wrapper frames // Extract location info const locationMatch = location.match(/(?:at\s+)?(?:.*?\s+)?(?:\()?(.+?):(\d+):(\d+)/); // Send to capture system window.postMessage({ type: 'enhanced-console', level, args: args.map(arg => { try { return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); } catch { return String(arg); } }), stack, location: locationMatch ? { url: locationMatch[1], line: parseInt(locationMatch[2]), column: parseInt(locationMatch[3]) } : null, timestamp: Date.now() }, '*'); }; // Override console methods console.error = (...args) => enhancedConsole('error', ...args); console.warn = (...args) => enhancedConsole('warn', ...args); console.log = (...args) => enhancedConsole('log', ...args); console.info = (...args) => enhancedConsole('info', ...args); console.debug = (...args) => enhancedConsole('debug', ...args); // Listen for unhandled promise rejections window.addEventListener('unhandledrejection', event => { enhancedConsole('error', 'Unhandled Promise Rejection:', event.reason); }); // Listen for general errors window.addEventListener('error', event => { enhancedConsole('error', 'JavaScript Error:', { message: event.message, filename: event.filename, line: event.lineno, column: event.colno, error: event.error }); }); }); // Listen for enhanced console messages await this.page.addInitScript(() => { window.addEventListener('message', (event) => { if (event.data.type === 'enhanced-console') { // This will be captured by Playwright's console handler console.log('ENHANCED_CONSOLE_CAPTURE', JSON.stringify(event.data)); } }); }); } /** * Handle console messages from Playwright */ async handleConsoleMessage(msg) { const text = msg.text(); const location = msg.location(); // Check if this is an enhanced capture if (text.startsWith('ENHANCED_CONSOLE_CAPTURE')) { try { const data = JSON.parse(text.substring('ENHANCED_CONSOLE_CAPTURE'.length + 1)); this.logs.push({ level: data.level, timestamp: data.timestamp, text: data.args.join(' '), location: data.location, stackTrace: data.stack, args: data.args, category: this.categorizeLog(data.level, data.args.join(' ')) }); return; } catch { // Fall through to normal handling } } // Normal console message handling const log = { level: this.mapConsoleType(msg.type()), timestamp: Date.now(), text, location: location ? { url: location.url, lineNumber: location.lineNumber, columnNumber: location.columnNumber } : undefined, category: this.categorizeLog(msg.type(), text) }; // Try to get stack trace for errors if (msg.type() === 'error') { try { const args = await Promise.all(msg.args().map(arg => arg.jsonValue())); log.args = args; // Look for Error objects const errorObj = args.find(arg => arg && typeof arg === 'object' && arg.stack); if (errorObj) { log.stackTrace = errorObj.stack; } } catch { // Ignore if we can't get args } } this.logs.push(log); } /** * Handle page errors */ handlePageError(error) { this.logs.push({ level: 'error', timestamp: Date.now(), text: error.message, stackTrace: error.stack, category: 'javascript' }); } /** * Map Playwright console types to our levels */ mapConsoleType(type) { switch (type) { case 'error': return 'error'; case 'warning': return 'warning'; case 'info': return 'info'; case 'debug': return 'debug'; default: return 'log'; } } /** * Categorize log messages */ categorizeLog(level, text) { const lowerText = text.toLowerCase(); if (lowerText.includes('uncaught') || lowerText.includes('exception')) { return 'javascript'; } if (lowerText.includes('failed to load') || lowerText.includes('404') || lowerText.includes('network')) { return 'network'; } if (lowerText.includes('cors') || lowerText.includes('cross-origin')) { return 'security'; } if (lowerText.includes('deprecated') || lowerText.includes('deprecation')) { return 'deprecation'; } if (lowerText.includes('performance') || lowerText.includes('slow')) { return 'performance'; } if (lowerText.includes('react') || lowerText.includes('vue') || lowerText.includes('angular')) { return 'framework'; } return 'general'; } /** * Extract error pattern for grouping */ extractErrorPattern(text) { // Remove specific values to find patterns return text .replace(/\b\d+\b/g, 'N') // Replace numbers .replace(/["'].*?["']/g, 'STR') // Replace strings .replace(/\s+/g, ' ') // Normalize whitespace .substring(0, 100); // Limit length } /** * Generate recommendations based on analysis */ generateRecommendations(summary, criticalErrors) { const recommendations = []; // High error rate if (summary.errorCount > 10) { recommendations.push('🚨 High error count detected. Focus on resolving JavaScript errors first.'); } // Network issues if (summary.categories.network > 5) { recommendations.push('🌐 Multiple network errors. Check API endpoints and CORS configuration.'); } // Security issues if (summary.categories.security > 0) { recommendations.push('🔒 Security warnings detected. Review CORS and CSP policies.'); } // Performance issues if (summary.categories.performance > 0) { recommendations.push('⚡ Performance warnings found. Consider profiling and optimization.'); } // Deprecation warnings if (summary.categories.deprecation > 0) { recommendations.push('⚠️ Deprecation warnings present. Update dependencies or API usage.'); } // Framework-specific issues if (summary.categories.framework > 5) { recommendations.push('🛠️ Framework errors detected. Check component lifecycle and state management.'); } // Check for specific error patterns for (const error of criticalErrors) { if (error.text.includes('Cannot read property') || error.text.includes('undefined')) { recommendations.push('💡 Null/undefined errors found. Add proper null checks and default values.'); break; } if (error.text.includes('Failed to fetch') || error.text.includes('NetworkError')) { recommendations.push('📡 Network connectivity issues. Verify backend services are running.'); break; } } return [...new Set(recommendations)]; // Remove duplicates } /** * Format logs as markdown */ formatAsMarkdown(analysis) { let report = '# Console Log Report\n\n'; if (analysis) { report += '## Summary\n\n'; report += `- **Total Logs**: ${analysis.summary.totalLogs}\n`; report += `- **Errors**: ${analysis.summary.errorCount} 🔴\n`; report += `- **Warnings**: ${analysis.summary.warningCount} 🟡\n`; report += `- **Info**: ${analysis.summary.infoCount} 🔵\n\n`; if (Object.keys(analysis.summary.categories).length > 0) { report += '### Categories\n\n'; for (const [category, count] of Object.entries(analysis.summary.categories)) { report += `- **${category}**: ${count}\n`; } report += '\n'; } if (analysis.recommendations.length > 0) { report += '## Recommendations\n\n'; for (const rec of analysis.recommendations) { report += `- ${rec}\n`; } report += '\n'; } if (analysis.criticalErrors.length > 0) { report += '## Critical Errors\n\n'; for (const error of analysis.criticalErrors) { report += `### ${new Date(error.timestamp).toISOString()}\n`; report += `**Message**: ${error.text}\n\n`; if (error.location) { report += `**Location**: ${error.location.url}:${error.location.lineNumber}:${error.location.columnNumber}\n\n`; } if (error.stackTrace) { report += '**Stack Trace**:\n```\n' + error.stackTrace + '\n```\n\n'; } } } } report += '## All Logs\n\n'; for (const log of this.logs.slice(0, 100)) { // Limit to 100 logs const icon = { error: '🔴', warning: '🟡', info: '🔵', debug: '🟢', log: '⚪' }[log.level]; report += `${icon} **[${new Date(log.timestamp).toLocaleTimeString()}]** ${log.text}\n`; if (log.location) { report += ` _${log.location.url}:${log.location.lineNumber}_\n`; } report += '\n'; } return report; } /** * Format logs as plain text */ formatAsText(analysis) { let report = 'CONSOLE LOG REPORT\n==================\n\n'; if (analysis) { report += `Total: ${analysis.summary.totalLogs} | `; report += `Errors: ${analysis.summary.errorCount} | `; report += `Warnings: ${analysis.summary.warningCount} | `; report += `Info: ${analysis.summary.infoCount}\n\n`; if (analysis.recommendations.length > 0) { report += 'RECOMMENDATIONS:\n'; for (const rec of analysis.recommendations) { report += ` - ${rec}\n`; } report += '\n'; } } report += 'LOGS:\n'; for (const log of this.logs) { const timestamp = new Date(log.timestamp).toLocaleTimeString(); report += `[${timestamp}] ${log.level.toUpperCase()}: ${log.text}\n`; if (log.location) { report += ` at ${log.location.url}:${log.location.lineNumber}\n`; } } return report; } } //# sourceMappingURL=enhanced-console-capture.js.map