Back to blog
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.