browser-connect-mcp
Version:
MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance
254 lines • 11.4 kB
JavaScript
export class ErrorCorrelator {
TIME_WINDOW = 5000; // 5 seconds
SIMILAR_ERROR_THRESHOLD = 0.8;
correlateErrors(messages, requests, responses) {
const correlations = [];
const responseMap = new Map(responses.map(r => [r.requestId, r]));
// 1. Find console errors related to network failures
correlations.push(...this.correlateNetworkErrors(messages, requests, responseMap));
// 2. Find cascading errors
correlations.push(...this.findCascadingErrors(messages));
// 3. Find repeated errors
correlations.push(...this.findRepeatedErrors(messages));
// 4. Identify root causes
this.identifyRootCauses(correlations);
return correlations;
}
correlateNetworkErrors(messages, requests, responseMap) {
const correlations = [];
const errorMessages = messages.filter(m => m.level === 'error');
for (const request of requests) {
const response = responseMap.get(request.requestId);
if (!response || response.status < 400)
continue;
// Find console errors within time window
const relatedErrors = errorMessages.filter(msg => Math.abs(msg.timestamp - response.timestamp) <= this.TIME_WINDOW &&
(msg.text.includes(request.url) ||
msg.text.includes(String(response.status)) ||
this.isNetworkErrorMessage(msg.text)));
if (relatedErrors.length > 0) {
correlations.push({
id: `net-error-${request.requestId}`,
type: 'console-network',
severity: response.status >= 500 ? 'high' : 'medium',
relatedItems: {
consoleMessages: relatedErrors,
networkRequests: [{ request, response }],
},
summary: `Network error ${response.status} on ${request.url} with ${relatedErrors.length} related console errors`,
suggestions: this.getNetworkErrorSuggestions(response.status, request, relatedErrors),
timeSpan: {
start: Math.min(request.timestamp, ...relatedErrors.map(e => e.timestamp)),
end: Math.max(response.timestamp, ...relatedErrors.map(e => e.timestamp)),
},
});
}
}
return correlations;
}
findCascadingErrors(messages) {
const correlations = [];
const errorMessages = messages.filter(m => m.level === 'error');
// Sort by timestamp
const sorted = [...errorMessages].sort((a, b) => a.timestamp - b.timestamp);
for (let i = 0; i < sorted.length; i++) {
const cascade = [sorted[i]];
let j = i + 1;
// Find errors that might be caused by the initial error
while (j < sorted.length && sorted[j].timestamp - sorted[i].timestamp <= this.TIME_WINDOW) {
if (this.isPotentiallyCaused(sorted[i], sorted[j])) {
cascade.push(sorted[j]);
}
j++;
}
if (cascade.length > 2) {
correlations.push({
id: `cascade-${i}`,
type: 'cascade',
severity: cascade.length > 5 ? 'high' : 'medium',
relatedItems: {
consoleMessages: cascade,
},
summary: `Cascading errors: ${cascade.length} errors starting with "${cascade[0].text.substring(0, 50)}..."`,
suggestions: [
'Fix the initial error first, as it may be causing the subsequent errors',
'Check for missing error handling in promise chains',
'Verify that resources are loaded in the correct order',
],
timeSpan: {
start: cascade[0].timestamp,
end: cascade[cascade.length - 1].timestamp,
},
});
// Skip the cascaded errors in the outer loop
i += cascade.length - 1;
}
}
return correlations;
}
findRepeatedErrors(messages) {
const correlations = [];
const errorMessages = messages.filter(m => m.level === 'error');
const errorGroups = new Map();
// Group similar errors
for (const error of errorMessages) {
let foundGroup = false;
for (const [, group] of errorGroups) {
if (this.areSimilarErrors(error.text, group[0].text)) {
group.push(error);
foundGroup = true;
break;
}
}
if (!foundGroup) {
errorGroups.set(error.text, [error]);
}
}
// Create correlations for repeated errors
for (const [, group] of errorGroups) {
if (group.length >= 3) {
const timeSpan = group[group.length - 1].timestamp - group[0].timestamp;
const frequency = timeSpan > 0 ? (group.length / timeSpan) * 1000 : 0; // errors per second
correlations.push({
id: `repeated-${group[0].timestamp}`,
type: 'repeated-error',
severity: frequency > 1 ? 'high' : 'medium',
relatedItems: {
consoleMessages: group,
},
summary: `Repeated error (${group.length} times): "${group[0].text.substring(0, 50)}..."`,
suggestions: this.getRepeatedErrorSuggestions(group[0], frequency),
timeSpan: {
start: group[0].timestamp,
end: group[group.length - 1].timestamp,
},
});
}
}
return correlations;
}
identifyRootCauses(correlations) {
for (const correlation of correlations) {
if (correlation.type === 'cascade' && correlation.relatedItems.consoleMessages) {
const firstError = correlation.relatedItems.consoleMessages[0];
correlation.rootCause = {
type: this.classifyError(firstError.text),
message: firstError.text,
source: firstError.url || 'unknown',
};
}
else if (correlation.type === 'console-network' && correlation.relatedItems.networkRequests) {
const netError = correlation.relatedItems.networkRequests[0];
correlation.rootCause = {
type: 'network',
message: `HTTP ${netError.response?.status} on ${netError.request.method} ${netError.request.url}`,
source: netError.request.url,
};
}
}
}
isNetworkErrorMessage(text) {
const patterns = [
/failed to fetch/i,
/network error/i,
/ERR_NETWORK/i,
/ERR_INTERNET_DISCONNECTED/i,
/ERR_NAME_NOT_RESOLVED/i,
/CORS/i,
/xhr.*failed/i,
/fetch.*failed/i,
];
return patterns.some(pattern => pattern.test(text));
}
isPotentiallyCaused(first, second) {
// Check if errors are from the same source
if (first.url && first.url === second.url) {
return true;
}
// Check if second error mentions something from first error
const firstKeywords = this.extractKeywords(first.text);
const secondText = second.text.toLowerCase();
return firstKeywords.some(keyword => secondText.includes(keyword));
}
areSimilarErrors(text1, text2) {
// Remove variable parts like IDs, timestamps, etc.
const normalize = (text) => {
return text
.replace(/\b\d+\b/g, 'NUM')
.replace(/\b[a-f0-9]{8,}\b/gi, 'ID')
.replace(/https?:\/\/[^\s]+/g, 'URL')
.toLowerCase()
.trim();
};
const normalized1 = normalize(text1);
const normalized2 = normalize(text2);
// Simple similarity check
if (normalized1 === normalized2)
return true;
// Check if they share significant keywords
const keywords1 = this.extractKeywords(text1);
const keywords2 = this.extractKeywords(text2);
const intersection = keywords1.filter(k => keywords2.includes(k));
return intersection.length / Math.min(keywords1.length, keywords2.length) >= this.SIMILAR_ERROR_THRESHOLD;
}
extractKeywords(text) {
const stopWords = ['the', 'is', 'at', 'in', 'on', 'and', 'or', 'to', 'a', 'an'];
return text
.toLowerCase()
.split(/\W+/)
.filter(word => word.length > 2 && !stopWords.includes(word));
}
classifyError(text) {
if (/TypeError/i.test(text))
return 'TypeError';
if (/ReferenceError/i.test(text))
return 'ReferenceError';
if (/SyntaxError/i.test(text))
return 'SyntaxError';
if (/RangeError/i.test(text))
return 'RangeError';
if (/URIError/i.test(text))
return 'URIError';
if (/SecurityError/i.test(text))
return 'SecurityError';
if (/Network|fetch|xhr/i.test(text))
return 'NetworkError';
return 'UnknownError';
}
getNetworkErrorSuggestions(status, request, errors) {
const suggestions = [];
if (status === 404) {
suggestions.push('Check if the URL is correct and the resource exists');
suggestions.push('Verify API endpoint configuration');
}
else if (status === 401 || status === 403) {
suggestions.push('Check authentication tokens and credentials');
suggestions.push('Verify user permissions for this resource');
}
else if (status >= 500) {
suggestions.push('Server error - check server logs for more details');
suggestions.push('Implement retry logic with exponential backoff');
}
if (errors.some(e => /CORS/i.test(e.text))) {
suggestions.push('Configure CORS headers on the server');
suggestions.push('Check if credentials are being sent correctly');
}
return suggestions;
}
getRepeatedErrorSuggestions(error, frequency) {
const suggestions = [];
if (frequency > 1) {
suggestions.push('Error is occurring rapidly - possible infinite loop or recursive call');
suggestions.push('Add debouncing or throttling to prevent repeated errors');
}
if (error.stackTrace) {
suggestions.push('Check the stack trace to identify the source of repeated calls');
}
if (/undefined|null/i.test(error.text)) {
suggestions.push('Add null/undefined checks before accessing properties');
suggestions.push('Ensure all required data is loaded before use');
}
return suggestions;
}
}
//# sourceMappingURL=error-correlation.js.map