browser-connect-mcp
Version:
MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance
462 lines • 20.5 kB
JavaScript
export class IntelligentLogAnalyzer {
analyzeLogsIntelligently(messages, requests, responses) {
const insights = [];
const responseMap = new Map(responses.map(r => [r.requestId, r]));
// 1. Detect patterns in logs
insights.push(...this.detectLogPatterns(messages));
// 2. Find anomalies
insights.push(...this.detectAnomalies(messages, requests, responseMap));
// 3. Analyze trends
insights.push(...this.analyzeTrends(messages, requests, responseMap));
// 4. Generate smart suggestions
insights.push(...this.generateSuggestions(messages, requests, responseMap));
// Sort by confidence and impact
return insights.sort((a, b) => {
const impactOrder = { high: 0, medium: 1, low: 2 };
if (a.impact && b.impact && a.impact !== b.impact) {
return impactOrder[a.impact] - impactOrder[b.impact];
}
return b.confidence - a.confidence;
});
}
detectLogPatterns(messages) {
const insights = [];
// Pattern: Excessive logging
const messageFrequency = this.calculateMessageFrequency(messages);
if (messageFrequency > 10) { // More than 10 messages per second
insights.push({
id: 'pattern-excessive-logging',
type: 'pattern',
category: 'performance',
title: 'Excessive console logging detected',
description: `Console is receiving ${messageFrequency.toFixed(1)} messages per second, which may impact performance`,
confidence: Math.min(messageFrequency / 20, 1),
evidence: {
metrics: {
messagesPerSecond: messageFrequency,
totalMessages: messages.length,
}
},
recommendations: [
'Reduce console logging in production',
'Use conditional logging based on environment',
'Consider using a proper logging library with levels',
],
impact: messageFrequency > 50 ? 'high' : 'medium',
});
}
// Pattern: Memory leak indicators
const memoryPatterns = messages.filter(m => /memory|heap|allocation|garbage collection/i.test(m.text));
if (memoryPatterns.length > 5) {
insights.push({
id: 'pattern-memory-issues',
type: 'pattern',
category: 'performance',
title: 'Potential memory issues detected',
description: 'Multiple console messages related to memory management found',
confidence: Math.min(memoryPatterns.length / 10, 1),
evidence: {
messages: memoryPatterns.slice(0, 5),
metrics: {
memoryRelatedMessages: memoryPatterns.length,
}
},
recommendations: [
'Profile memory usage in DevTools',
'Check for detached DOM nodes',
'Review event listener cleanup',
'Look for circular references',
],
impact: 'high',
});
}
// Pattern: Development artifacts in production
const devPatterns = messages.filter(m => /console\.log|TODO|FIXME|DEBUG|localhost|test/i.test(m.text));
if (devPatterns.length > 10) {
insights.push({
id: 'pattern-dev-artifacts',
type: 'pattern',
category: 'debugging',
title: 'Development artifacts detected',
description: 'Console contains development-related messages that should be removed in production',
confidence: 0.8,
evidence: {
messages: devPatterns.slice(0, 5),
metrics: {
devMessages: devPatterns.length,
}
},
recommendations: [
'Remove console.log statements before production',
'Use environment-based logging',
'Configure build tools to strip debug code',
],
impact: 'low',
});
}
return insights;
}
detectAnomalies(messages, requests, responseMap) {
const insights = [];
// Anomaly: Sudden spike in errors
const errorTimeline = this.createErrorTimeline(messages);
const spikes = this.detectSpikes(errorTimeline);
for (const spike of spikes) {
insights.push({
id: `anomaly-error-spike-${spike.timestamp}`,
type: 'anomaly',
category: 'error',
title: 'Sudden spike in errors detected',
description: `Error rate increased ${spike.magnitude}x at ${new Date(spike.timestamp).toISOString()}`,
confidence: Math.min(spike.magnitude / 5, 1),
evidence: {
messages: spike.errors,
metrics: {
normalRate: spike.normalRate,
spikeRate: spike.spikeRate,
magnitude: spike.magnitude,
}
},
recommendations: [
'Investigate what changed at this time',
'Check for failed deployments or API changes',
'Review error messages for common patterns',
],
impact: spike.magnitude > 10 ? 'high' : 'medium',
});
}
// Anomaly: Unusual network patterns
const requestPatterns = this.analyzeRequestPatterns(requests, responseMap);
if (requestPatterns.hasUnusualTraffic) {
insights.push({
id: 'anomaly-network-pattern',
type: 'anomaly',
category: 'performance',
title: 'Unusual network traffic pattern detected',
description: requestPatterns.description,
confidence: requestPatterns.confidence,
evidence: {
metrics: requestPatterns.metrics,
},
recommendations: [
'Check for polling intervals that are too frequent',
'Look for duplicate or redundant requests',
'Verify API endpoint health',
],
impact: 'medium',
});
}
return insights;
}
analyzeTrends(messages, requests, responseMap) {
const insights = [];
// Trend: Increasing response times
const responseTrend = this.analyzeResponseTimeTrend(requests, responseMap);
if (responseTrend.isIncreasing) {
insights.push({
id: 'trend-response-time',
type: 'trend',
category: 'performance',
title: 'Response times are increasing',
description: `Average response time increased from ${responseTrend.startAvg}ms to ${responseTrend.endAvg}ms`,
confidence: responseTrend.confidence,
evidence: {
metrics: {
startAverage: responseTrend.startAvg,
endAverage: responseTrend.endAvg,
percentIncrease: responseTrend.percentIncrease,
}
},
recommendations: [
'Monitor server performance metrics',
'Check for increased load or traffic',
'Review recent code changes for performance issues',
'Consider implementing caching',
],
impact: responseTrend.percentIncrease > 50 ? 'high' : 'medium',
});
}
// Trend: Error rate changes
const errorTrend = this.analyzeErrorTrend(messages);
if (errorTrend.isSignificant) {
insights.push({
id: 'trend-error-rate',
type: 'trend',
category: 'error',
title: errorTrend.isIncreasing ? 'Error rate is increasing' : 'Error rate is decreasing',
description: `Error rate changed by ${Math.abs(errorTrend.percentChange)}% over the session`,
confidence: errorTrend.confidence,
evidence: {
metrics: {
startRate: errorTrend.startRate,
endRate: errorTrend.endRate,
percentChange: errorTrend.percentChange,
}
},
recommendations: errorTrend.isIncreasing ? [
'Investigate recent deployments',
'Check for external service issues',
'Review error handling code',
] : [
'Document what fixed the issues',
'Add tests to prevent regression',
],
impact: Math.abs(errorTrend.percentChange) > 50 ? 'high' : 'medium',
});
}
return insights;
}
generateSuggestions(messages, requests, responseMap) {
const insights = [];
// Suggestion: API consolidation
const apiAnalysis = this.analyzeAPICalls(requests, responseMap);
if (apiAnalysis.hasDuplicates) {
insights.push({
id: 'suggestion-api-consolidation',
type: 'suggestion',
category: 'performance',
title: 'API calls can be consolidated',
description: 'Multiple similar API calls detected that could be batched or cached',
confidence: 0.9,
evidence: {
metrics: {
duplicateRequests: apiAnalysis.duplicates,
potentialSavings: `${apiAnalysis.potentialSavings}ms`,
}
},
recommendations: [
'Implement request batching for similar endpoints',
'Use caching for frequently requested data',
'Consider GraphQL or similar to reduce requests',
],
impact: 'medium',
});
}
// Suggestion: Error handling improvements
const unhandledErrors = messages.filter(m => m.level === 'error' && /uncaught|unhandled/i.test(m.text));
if (unhandledErrors.length > 0) {
insights.push({
id: 'suggestion-error-handling',
type: 'suggestion',
category: 'error',
title: 'Improve error handling',
description: `Found ${unhandledErrors.length} unhandled errors that need proper error boundaries`,
confidence: 1,
evidence: {
messages: unhandledErrors.slice(0, 3),
},
recommendations: [
'Add try-catch blocks around async operations',
'Implement error boundaries in React components',
'Add global error handlers for uncaught exceptions',
'Log errors to monitoring service',
],
impact: 'high',
});
}
// Suggestion: Performance optimizations
const performanceIssues = this.identifyPerformanceIssues(messages, requests, responseMap);
if (performanceIssues.length > 0) {
insights.push({
id: 'suggestion-performance',
type: 'suggestion',
category: 'performance',
title: 'Performance optimization opportunities',
description: 'Several performance improvements can be made',
confidence: 0.8,
evidence: {
metrics: {
issues: performanceIssues,
}
},
recommendations: performanceIssues.map(issue => issue.recommendation),
impact: 'medium',
});
}
return insights;
}
// Helper methods
calculateMessageFrequency(messages) {
if (messages.length < 2)
return 0;
const timeSpan = messages[messages.length - 1].timestamp - messages[0].timestamp;
if (timeSpan === 0)
return messages.length;
return (messages.length / timeSpan) * 1000; // messages per second
}
createErrorTimeline(messages) {
const errors = messages.filter(m => m.level === 'error');
const buckets = new Map();
const bucketSize = 60000; // 1 minute buckets
for (const error of errors) {
const bucket = Math.floor(error.timestamp / bucketSize) * bucketSize;
buckets.set(bucket, (buckets.get(bucket) || 0) + 1);
}
return Array.from(buckets.entries())
.map(([timestamp, count]) => ({ timestamp, count }))
.sort((a, b) => a.timestamp - b.timestamp);
}
detectSpikes(timeline) {
const spikes = [];
const window = 3; // Look at 3 buckets at a time
for (let i = window; i < timeline.length; i++) {
const previous = timeline.slice(i - window, i);
const current = timeline[i];
const avgPrevious = previous.reduce((sum, p) => sum + p.count, 0) / window;
const magnitude = current.count / (avgPrevious || 1);
if (magnitude > 3) { // 3x increase is a spike
spikes.push({
timestamp: current.timestamp,
normalRate: avgPrevious,
spikeRate: current.count,
magnitude,
errors: [], // Would need to filter actual messages
});
}
}
return spikes;
}
analyzeRequestPatterns(requests, _responseMap) {
// Group requests by endpoint
const endpoints = new Map();
for (const request of requests) {
const url = new URL(request.url);
const endpoint = `${url.pathname}`;
if (!endpoints.has(endpoint)) {
endpoints.set(endpoint, []);
}
endpoints.get(endpoint).push(request);
}
// Look for unusual patterns
let hasUnusualTraffic = false;
let description = '';
const metrics = {};
for (const [endpoint, reqs] of endpoints) {
if (reqs.length > 50) {
hasUnusualTraffic = true;
description = `Endpoint ${endpoint} called ${reqs.length} times`;
metrics.hotEndpoint = endpoint;
metrics.callCount = reqs.length;
// Check request frequency
const timeSpan = reqs[reqs.length - 1].timestamp - reqs[0].timestamp;
const frequency = (reqs.length / timeSpan) * 1000;
if (frequency > 1) { // More than 1 request per second
metrics.requestsPerSecond = frequency;
description += ` at ${frequency.toFixed(1)} requests/second`;
}
}
}
return {
hasUnusualTraffic,
description,
confidence: hasUnusualTraffic ? 0.8 : 0,
metrics,
};
}
analyzeResponseTimeTrend(requests, responseMap) {
const timeSeries = [];
for (const request of requests) {
const response = responseMap.get(request.requestId);
if (response && response.responseTime) {
timeSeries.push({
timestamp: request.timestamp,
responseTime: response.responseTime,
});
}
}
if (timeSeries.length < 10) {
return { isIncreasing: false };
}
// Compare first third with last third
const third = Math.floor(timeSeries.length / 3);
const firstThird = timeSeries.slice(0, third);
const lastThird = timeSeries.slice(-third);
const avgFirst = firstThird.reduce((sum, t) => sum + t.responseTime, 0) / firstThird.length;
const avgLast = lastThird.reduce((sum, t) => sum + t.responseTime, 0) / lastThird.length;
const percentIncrease = ((avgLast - avgFirst) / avgFirst) * 100;
return {
isIncreasing: percentIncrease > 20,
startAvg: Math.round(avgFirst),
endAvg: Math.round(avgLast),
percentIncrease: Math.round(percentIncrease),
confidence: Math.min(Math.abs(percentIncrease) / 50, 1),
};
}
analyzeErrorTrend(messages) {
const errors = messages.filter(m => m.level === 'error');
const total = messages.length;
if (errors.length < 5 || total < 50) {
return { isSignificant: false };
}
// Split into first half and second half
const midpoint = Math.floor(total / 2);
const firstHalf = messages.slice(0, midpoint);
const secondHalf = messages.slice(midpoint);
const errorsFirst = firstHalf.filter(m => m.level === 'error').length;
const errorsSecond = secondHalf.filter(m => m.level === 'error').length;
const rateFirst = errorsFirst / firstHalf.length;
const rateSecond = errorsSecond / secondHalf.length;
const percentChange = ((rateSecond - rateFirst) / rateFirst) * 100;
return {
isSignificant: Math.abs(percentChange) > 30,
isIncreasing: percentChange > 0,
startRate: Math.round(rateFirst * 100),
endRate: Math.round(rateSecond * 100),
percentChange: Math.round(percentChange),
confidence: Math.min(Math.abs(percentChange) / 50, 1),
};
}
analyzeAPICalls(requests, responseMap) {
const apiCalls = requests.filter(r => r.url.includes('/api/'));
const duplicates = new Map();
// Find duplicate calls
for (const request of apiCalls) {
const key = `${request.method} ${request.url}`;
duplicates.set(key, (duplicates.get(key) || 0) + 1);
}
const duplicateCount = Array.from(duplicates.values()).filter(c => c > 1).length;
const totalDuplicates = Array.from(duplicates.values()).reduce((sum, c) => sum + Math.max(0, c - 1), 0);
// Calculate potential time savings
let potentialSavings = 0;
for (const [key, count] of duplicates) {
if (count > 1) {
const matchingRequests = apiCalls.filter(r => `${r.method} ${r.url}` === key);
for (const req of matchingRequests) {
const response = responseMap.get(req.requestId);
if (response && response.responseTime) {
potentialSavings += response.responseTime * (count - 1) / count;
}
}
}
}
return {
hasDuplicates: duplicateCount > 0,
duplicates: totalDuplicates,
potentialSavings: Math.round(potentialSavings),
};
}
identifyPerformanceIssues(messages, requests, responseMap) {
const issues = [];
// Check for large payloads
const largeResponses = Array.from(responseMap.values()).filter(r => r.encodedDataLength && r.encodedDataLength > 1024 * 1024 // 1MB
);
if (largeResponses.length > 0) {
issues.push({
type: 'large-payload',
count: largeResponses.length,
recommendation: 'Implement pagination or lazy loading for large data sets',
});
}
// Check for render performance warnings
const renderWarnings = messages.filter(m => /forced reflow|layout thrashing|recalculate style/i.test(m.text));
if (renderWarnings.length > 0) {
issues.push({
type: 'render-performance',
count: renderWarnings.length,
recommendation: 'Batch DOM updates and avoid layout thrashing',
});
}
return issues;
}
}
//# sourceMappingURL=log-analyzer.js.map