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
JavaScript
/**
* 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