InspectionJanuary 1, 2024ยท 9 min read
JavaScript Error Tracking: Catch Bugs Before Users Report Them
A practical Fusebox guide to javascript error tracking.
JavaScript Error Tracking: Catch Bugs Before Users Report Them
Published: January 2024
Reading time: 8 minutes
JavaScript errors are silent killers. They break features, frustrate users, and hurt conversions. Most sites have errors they don't even know about. Here's how to detect, track, and fix JavaScript errors in production.
Why JavaScript Errors Matter
- Users don't report errors - They just leave
- Errors compound - One error often causes more
- Browser differences - Works in Chrome, breaks in Safari
- Hidden failures - Payment forms, checkout, key features
- SEO impact - Google can't index broken pages
Quick Error Detection
1. Current Page Error Check
// Immediate error detection for current page
(function detectCurrentErrors() {
console.log('๐จ JavaScript Error Detection\n');
let errorCount = 0;
const errors = [];
// Set up error listener
window.addEventListener('error', (e) => {
errorCount++;
errors.push({
message: e.message,
source: e.filename,
line: e.lineno,
column: e.colno,
stack: e.error?.stack,
timestamp: new Date().toLocaleTimeString()
});
console.error(`Error #${errorCount}:`, e.message);
console.log(` File: ${e.filename}`);
console.log(` Line: ${e.lineno}, Column: ${e.colno}`);
if (e.error?.stack) {
console.log(` Stack:`, e.error.stack);
}
});
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (e) => {
errorCount++;
console.error(`Unhandled Promise Rejection #${errorCount}:`, e.reason);
errors.push({
message: `Unhandled Promise: ${e.reason}`,
promise: e.promise,
timestamp: new Date().toLocaleTimeString()
});
});
// Check for existing errors in console
const originalError = console.error;
console.error = function(...args) {
errorCount++;
errors.push({
message: args.join(' '),
type: 'console.error',
timestamp: new Date().toLocaleTimeString()
});
originalError.apply(console, args);
};
// Summary after page settles
setTimeout(() => {
if (errorCount > 0) {
console.log(`\nโ Total errors detected: ${errorCount}`);
console.log('Error summary:', errors);
} else {
console.log('โ
No JavaScript errors detected');
}
}, 3000);
})();
2. Error Pattern Analysis
// Analyze types of errors
function analyzeErrorPatterns() {
console.log('\n๐ Error Pattern Analysis:');
const errorPatterns = {
'Null/Undefined': /Cannot read prop|of undefined|of null/i,
'Network/Fetch': /fetch|Failed to fetch|NetworkError/i,
'Syntax Error': /SyntaxError|Unexpected token/i,
'Type Error': /TypeError|is not a function/i,
'Reference Error': /ReferenceError|is not defined/i,
'CORS': /CORS|Cross-Origin/i,
'Promise': /Unhandled Promise|Promise rejection/i
};
// Monitor for 5 seconds
const detectedPatterns = {};
window.addEventListener('error', (e) => {
const message = e.message;
Object.entries(errorPatterns).forEach(([pattern, regex]) => {
if (regex.test(message)) {
detectedPatterns[pattern] = (detectedPatterns[pattern] || 0) + 1;
}
});
});
// Report after delay
setTimeout(() => {
if (Object.keys(detectedPatterns).length > 0) {
console.log('Error types found:');
Object.entries(detectedPatterns).forEach(([type, count]) => {
console.log(` ${type}: ${count} occurrence(s)`);
});
}
}, 5000);
}
analyzeErrorPatterns();
Common JavaScript Errors
1. Cannot Read Property of Undefined
// Most common JavaScript error
function detectUndefinedErrors() {
console.log('\n๐ Checking for Undefined Access Patterns:');
// Common problematic patterns
const riskyPatterns = [
{
pattern: /user\.profile\.name/g,
risk: 'Accessing nested properties without checking'
},
{
pattern: /array\[0\]\.property/g,
risk: 'Assuming array has elements'
},
{
pattern: /response\.data\.items/g,
risk: 'Assuming API response structure'
},
{
pattern: /document\.querySelector\([^)]+\)\./g,
risk: 'Not checking if element exists'
}
];
// Check inline scripts
const scripts = Array.from(document.scripts).filter(s => !s.src);
let issues = 0;
scripts.forEach(script => {
const code = script.textContent;
riskyPatterns.forEach(({pattern, risk}) => {
if (pattern.test(code)) {
console.warn(`โ ๏ธ Risky pattern found: ${risk}`);
issues++;
}
});
});
if (issues === 0) {
console.log('โ
No obvious risky patterns detected');
}
// Provide safe alternatives
console.log('\n๐ก Safe Alternatives:');
console.log('// Instead of: user.profile.name');
console.log('// Use: user?.profile?.name');
console.log('// Or: user && user.profile && user.profile.name');
}
detectUndefinedErrors();
2. Network and Fetch Errors
// Detect network-related errors
function monitorNetworkErrors() {
console.log('\n๐ Network Error Monitoring:');
let networkErrors = 0;
// Override fetch to catch errors
const originalFetch = window.fetch;
window.fetch = function(...args) {
return originalFetch.apply(this, args)
.catch(error => {
networkErrors++;
console.error('Fetch error detected:', {
url: args[0],
error: error.message,
timestamp: new Date().toLocaleTimeString()
});
throw error;
});
};
// Monitor failed resource loads
window.addEventListener('error', (e) => {
if (e.target !== window) {
// Resource loading error
networkErrors++;
console.error('Resource loading error:', {
type: e.target.tagName,
src: e.target.src || e.target.href,
message: 'Failed to load resource'
});
}
}, true);
// Check for mixed content
if (location.protocol === 'https:') {
const insecureResources = Array.from(document.querySelectorAll('[src^="http:"], [href^="http:"]'));
if (insecureResources.length > 0) {
console.warn(`โ ๏ธ Mixed content warning: ${insecureResources.length} insecure resources`);
}
}
// Summary
setTimeout(() => {
if (networkErrors > 0) {
console.log(`\nโ Network errors detected: ${networkErrors}`);
} else {
console.log('\nโ
No network errors detected');
}
}, 3000);
}
monitorNetworkErrors();
3. Third-Party Script Errors
// Identify errors from third-party scripts
function trackThirdPartyErrors() {
console.log('\n๐ Third-Party Script Error Tracking:');
const thirdPartyErrors = {};
window.addEventListener('error', (e) => {
if (e.filename && !e.filename.includes(location.hostname)) {
const domain = new URL(e.filename).hostname;
if (!thirdPartyErrors[domain]) {
thirdPartyErrors[domain] = [];
}
thirdPartyErrors[domain].push({
message: e.message,
file: e.filename.split('/').pop(),
line: e.lineno
});
console.error(`Third-party error from ${domain}:`, e.message);
}
});
// Report after delay
setTimeout(() => {
const errorDomains = Object.keys(thirdPartyErrors);
if (errorDomains.length > 0) {
console.log('\n๐ Third-party errors by domain:');
errorDomains.forEach(domain => {
console.log(` ${domain}: ${thirdPartyErrors[domain].length} error(s)`);
});
} else {
console.log('โ
No third-party script errors detected');
}
}, 5000);
}
trackThirdPartyErrors();
Production Error Tracking
1. Error Tracking Implementation
// Basic error tracking system
class ErrorTracker {
constructor() {
this.errors = [];
this.setupListeners();
}
setupListeners() {
// Global error handler
window.addEventListener('error', (e) => {
this.trackError({
type: 'javascript',
message: e.message,
filename: e.filename,
lineno: e.lineno,
colno: e.colno,
stack: e.error?.stack,
userAgent: navigator.userAgent,
timestamp: Date.now(),
url: window.location.href
});
});
// Promise rejection handler
window.addEventListener('unhandledrejection', (e) => {
this.trackError({
type: 'unhandled_promise',
message: e.reason?.toString() || 'Unknown promise rejection',
stack: e.reason?.stack,
timestamp: Date.now(),
url: window.location.href
});
});
}
trackError(error) {
// Add error context
error.context = {
viewport: `${window.innerWidth}x${window.innerHeight}`,
connection: navigator.connection?.effectiveType,
memory: performance.memory?.usedJSHeapSize
? `${Math.round(performance.memory.usedJSHeapSize / 1048576)}MB`
: 'Unknown'
};
this.errors.push(error);
// Send to server (implement your endpoint)
this.sendToServer(error);
// Log locally
console.error('Tracked error:', error);
}
sendToServer(error) {
// Example: Send to your error tracking endpoint
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/errors', JSON.stringify(error));
} else {
fetch('/api/errors', {
method: 'POST',
body: JSON.stringify(error),
headers: { 'Content-Type': 'application/json' }
}).catch(() => {
// Silently fail - don't create more errors
});
}
}
getReport() {
const grouped = {};
this.errors.forEach(error => {
const key = `${error.message}_${error.filename}_${error.lineno}`;
grouped[key] = (grouped[key] || 0) + 1;
});
return {
total: this.errors.length,
unique: Object.keys(grouped).length,
byFrequency: Object.entries(grouped)
.sort((a, b) => b[1] - a[1])
.map(([key, count]) => ({ error: key, count }))
};
}
}
// Initialize tracker
const errorTracker = new ErrorTracker();
// Get report after some time
setTimeout(() => {
const report = errorTracker.getReport();
if (report.total > 0) {
console.log('\n๐ Error Report:', report);
}
}, 10000);
2. Error Context Collection
// Collect context for better debugging
function collectErrorContext() {
return {
// User context
user: {
id: window.userId || 'anonymous',
session: window.sessionId || generateSessionId(),
actions: getRecentUserActions()
},
// Technical context
technical: {
browser: getBrowserInfo(),
viewport: {
width: window.innerWidth,
height: window.innerHeight,
orientation: window.orientation
},
performance: {
memory: performance.memory?.usedJSHeapSize,
timing: performance.timing.loadEventEnd - performance.timing.navigationStart
}
},
// Page context
page: {
url: window.location.href,
referrer: document.referrer,
title: document.title,
loadTime: new Date() - performance.timing.navigationStart
},
// State context
state: {
localStorage: Object.keys(localStorage).length,
sessionStorage: Object.keys(sessionStorage).length,
cookies: document.cookie.split(';').length
}
};
}
function getRecentUserActions() {
// Implement action tracking
return window.userActions || [];
}
function generateSessionId() {
return 'sess_' + Math.random().toString(36).substr(2, 9);
}
function getBrowserInfo() {
const ua = navigator.userAgent;
return {
name: /Chrome/.test(ua) ? 'Chrome' :
/Safari/.test(ua) ? 'Safari' :
/Firefox/.test(ua) ? 'Firefox' : 'Other',
version: ua.match(/(?:Chrome|Safari|Firefox)\/(\d+)/)?.[1] || 'Unknown'
};
}
Error Recovery Strategies
1. Graceful Error Handling
// Implement error boundaries for critical features
class ErrorBoundary {
constructor(element, fallbackContent) {
this.element = element;
this.fallbackContent = fallbackContent;
this.originalContent = element.innerHTML;
this.setupErrorHandling();
}
setupErrorHandling() {
// Wrap execution in try-catch
const originalAddEventListener = this.element.addEventListener;
this.element.addEventListener = (event, handler, ...args) => {
const wrappedHandler = (...eventArgs) => {
try {
return handler(...eventArgs);
} catch (error) {
this.handleError(error);
}
};
return originalAddEventListener.call(this.element, event, wrappedHandler, ...args);
};
}
handleError(error) {
console.error('Component error caught:', error);
// Show fallback UI
this.element.innerHTML = this.fallbackContent;
// Track error
if (window.errorTracker) {
window.errorTracker.trackError({
type: 'component_error',
message: error.message,
component: this.element.id || this.element.className,
stack: error.stack
});
}
// Attempt recovery
this.attemptRecovery();
}
attemptRecovery() {
setTimeout(() => {
console.log('Attempting to recover component...');
this.element.innerHTML = this.originalContent;
// Re-initialize component
}, 5000);
}
}
// Usage example
const criticalComponent = document.querySelector('#critical-feature');
if (criticalComponent) {
new ErrorBoundary(
criticalComponent,
'<div class="error-message">This feature is temporarily unavailable. Please refresh the page.</div>'
);
}
2. Retry Logic for Failed Operations
// Implement retry logic for critical operations
class RetryableOperation {
constructor(operation, options = {}) {
this.operation = operation;
this.maxRetries = options.maxRetries || 3;
this.retryDelay = options.retryDelay || 1000;
this.backoff = options.backoff || 2;
}
async execute() {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await this.operation();
} catch (error) {
lastError = error;
console.warn(`Attempt ${attempt} failed:`, error.message);
if (attempt < this.maxRetries) {
const delay = this.retryDelay * Math.pow(this.backoff, attempt - 1);
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Operation failed after ${this.maxRetries} attempts: ${lastError.message}`);
}
}
// Usage
const fetchWithRetry = new RetryableOperation(
() => fetch('/api/critical-data').then(r => r.json()),
{ maxRetries: 3, retryDelay: 1000 }
);
fetchWithRetry.execute()
.then(data => console.log('Success:', data))
.catch(error => console.error('Failed after retries:', error));
Performance Impact of Errors
Error Performance Monitor
// Monitor how errors affect performance
function monitorErrorPerformanceImpact() {
console.log('\nโก Error Performance Impact Analysis:');
const metrics = {
beforeErrors: {
memory: performance.memory?.usedJSHeapSize,
mainThreadTime: 0
},
afterErrors: {},
errorCount: 0
};
// Measure main thread blocking
let lastTime = performance.now();
const measureMainThread = () => {
const now = performance.now();
const delta = now - lastTime;
if (delta > 50) { // Blocked for >50ms
console.warn(`Main thread blocked for ${delta.toFixed(0)}ms`);
}
lastTime = now;
requestAnimationFrame(measureMainThread);
};
measureMainThread();
// Track errors and their impact
window.addEventListener('error', (e) => {
metrics.errorCount++;
// Check memory after error
if (performance.memory) {
const memoryIncrease = performance.memory.usedJSHeapSize - metrics.beforeErrors.memory;
console.log(`Memory increase after error: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
}
});
// Report after delay
setTimeout(() => {
if (metrics.errorCount > 0) {
console.log(`\nโ Errors impacted performance:`);
console.log(` Errors: ${metrics.errorCount}`);
if (performance.memory) {
const memoryIncrease = performance.memory.usedJSHeapSize - metrics.beforeErrors.memory;
console.log(` Memory impact: +${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
}
}
}, 5000);
}
monitorErrorPerformanceImpact();
Complete Error Audit
// Comprehensive error tracking audit
(function completeErrorAudit() {
console.log('๐จ Complete JavaScript Error Audit\n');
console.log('โ'.repeat(50));
const audit = {
errors: [],
patterns: {},
sources: {},
startTime: Date.now()
};
// Set up comprehensive error tracking
window.addEventListener('error', (e) => {
const error = {
message: e.message,
source: e.filename,
line: e.lineno,
column: e.colno,
timestamp: Date.now() - audit.startTime
};
audit.errors.push(error);
// Categorize by pattern
if (error.message.includes('undefined')) {
audit.patterns.undefined = (audit.patterns.undefined || 0) + 1;
} else if (error.message.includes('network')) {
audit.patterns.network = (audit.patterns.network || 0) + 1;
} else if (error.message.includes('promise')) {
audit.patterns.promise = (audit.patterns.promise || 0) + 1;
}
// Track by source
const sourceDomain = error.source.includes('http')
? new URL(error.source).hostname
: 'inline';
audit.sources[sourceDomain] = (audit.sources[sourceDomain] || 0) + 1;
});
window.addEventListener('unhandledrejection', (e) => {
audit.errors.push({
message: `Unhandled Promise: ${e.reason}`,
type: 'promise',
timestamp: Date.now() - audit.startTime
});
audit.patterns.promise = (audit.patterns.promise || 0) + 1;
});
// Generate report after 5 seconds
setTimeout(() => {
console.log('\n๐ Error Audit Report:');
console.log('โ'.repeat(50));
if (audit.errors.length === 0) {
console.log('โ
No JavaScript errors detected!');
} else {
console.log(`โ Total errors: ${audit.errors.length}`);
console.log('\nError Patterns:');
Object.entries(audit.patterns).forEach(([pattern, count]) => {
console.log(` ${pattern}: ${count}`);
});
console.log('\nError Sources:');
Object.entries(audit.sources).forEach(([source, count]) => {
console.log(` ${source}: ${count}`);
});
console.log('\nFirst 3 Errors:');
audit.errors.slice(0, 3).forEach((error, i) => {
console.log(` ${i + 1}. ${error.message}`);
console.log(` at ${error.source}:${error.line}:${error.column}`);
});
}
// Recommendations
console.log('\n๐ก Recommendations:');
if (audit.patterns.undefined > 0) {
console.log('- Use optional chaining (?.) for property access');
}
if (audit.patterns.network > 0) {
console.log('- Add error handling to all fetch/API calls');
}
if (audit.patterns.promise > 0) {
console.log('- Add .catch() to all promises');
}
if (Object.keys(audit.sources).length > 2) {
console.log('- Consider consolidating error tracking across scripts');
}
}, 5000);
})();
The Bottom Line
JavaScript errors are inevitable, but untracked errors are unacceptable:
- Every error costs conversions - Users leave broken sites
- Errors hide in production - Testing can't catch everything
- Browser differences matter - Test cross-browser
- Context is crucial - Know when, where, and why errors happen
- Recovery is possible - Plan for graceful failures
Track errors. Fix them fast. Your users will thank you.
Track JavaScript errors instantly: Fusebox monitors JavaScript errors, console output, and performance impact while you browse. Catch bugs before users do. $29 one-time purchase.