Debugging Tools & Techniques

Comprehensive guide to debugging tools, techniques, and best practices for troubleshooting applications.

Debugging Tools & Techniques

Comprehensive guide to debugging tools and techniques for effective troubleshooting.

Browser Developer Tools

Chrome DevTools

Console Debugging:

// Advanced console methods
console.log('Basic logging')
console.warn('Warning message')
console.error('Error message')
console.info('Info message')

// Structured logging
console.table([
  { name: 'Product A', price: 29.99, stock: 10 },
  { name: 'Product B', price: 49.99, stock: 5 },
])

// Timing operations
console.time('Database Query')
await fetchProducts()
console.timeEnd('Database Query')

// Grouping related logs
console.group('User Authentication')
console.log('Checking credentials...')
console.log('Validating session...')
console.log('Setting user context...')
console.groupEnd()

// Conditional logging
const DEBUG = process.env.NODE_ENV === 'development'
console.assert(user.isActive, 'User should be active')

// Stack traces
console.trace('Execution path')

// Custom styling
console.log('%c Success! ', 'background: green; color: white; padding: 2px')
console.log('%c Error! ', 'background: red; color: white; padding: 2px')

Network Tab Analysis:

// Monitor API calls
export function monitorApiCalls() {
  const originalFetch = window.fetch
  
  window.fetch = function(...args) {
    const url = args[0]
    const options = args[1] || {}
    
    console.group(`🌐 API Call: ${options.method || 'GET'} ${url}`)
    console.log('Request:', { url, options })
    console.time('Response Time')
    
    return originalFetch.apply(this, args)
      .then(response => {
        console.timeEnd('Response Time')
        console.log('Response:', {
          status: response.status,
          statusText: response.statusText,
          headers: [...response.headers.entries()],
        })
        console.groupEnd()
        return response
      })
      .catch(error => {
        console.timeEnd('Response Time')
        console.error('Error:', error)
        console.groupEnd()
        throw error
      })
  }
}

Performance Profiling

// Performance measurement
export class PerformanceProfiler {
  private marks: Map<string, number> = new Map()
  
  mark(name: string) {
    const timestamp = performance.now()
    this.marks.set(name, timestamp)
    performance.mark(name)
  }
  
  measure(name: string, startMark: string, endMark?: string) {
    if (endMark) {
      performance.measure(name, startMark, endMark)
    } else {
      performance.measure(name, startMark)
    }
    
    const measure = performance.getEntriesByName(name)[0]
    console.log(`⏱️ ${name}: ${measure.duration.toFixed(2)}ms`)
    
    return measure.duration
  }
  
  getMetrics() {
    return performance.getEntriesByType('measure')
  }
  
  clear() {
    performance.clearMarks()
    performance.clearMeasures()
    this.marks.clear()
  }
}

// Usage example
const profiler = new PerformanceProfiler()

export async function fetchProductsWithProfiling() {
  profiler.mark('fetch-start')
  
  try {
    const response = await fetch('/api/products')
    profiler.mark('fetch-end')
    
    const data = await response.json()
    profiler.mark('parse-end')
    
    profiler.measure('Network Time', 'fetch-start', 'fetch-end')
    profiler.measure('Parse Time', 'fetch-end', 'parse-end')
    profiler.measure('Total Time', 'fetch-start', 'parse-end')
    
    return data
  } catch (error) {
    profiler.mark('error')
    profiler.measure('Error Time', 'fetch-start', 'error')
    throw error
  }
}

React Debugging Tools

React DevTools

// Custom hooks for debugging
export function useDebugValue(value: any, formatter?: (value: any) => string) {
  React.useDebugValue(value, formatter)
}

export function useWhyDidYouUpdate(name: string, props: Record<string, any>) {
  const previous = useRef<Record<string, any>>()
  
  useEffect(() => {
    if (previous.current) {
      const allKeys = Object.keys({ ...previous.current, ...props })
      const changedProps: Record<string, { from: any; to: any }> = {}
      
      allKeys.forEach(key => {
        if (previous.current![key] !== props[key]) {
          changedProps[key] = {
            from: previous.current![key],
            to: props[key],
          }
        }
      })
      
      if (Object.keys(changedProps).length) {
        console.log('[why-did-you-update]', name, changedProps)
      }
    }
    
    previous.current = props
  })
}

// Component wrapper for debugging
export function withDebug<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  debugName?: string
) {
  const ComponentWithDebug = (props: P) => {
    const name = debugName || WrappedComponent.displayName || WrappedComponent.name
    
    useWhyDidYouUpdate(name, props)
    
    console.log(`🔄 Rendering ${name}`, props)
    
    return <WrappedComponent {...props} />
  }
  
  ComponentWithDebug.displayName = `withDebug(${debugName || 'Component'})`
  
  return ComponentWithDebug
}

State Debugging

// Redux DevTools integration
export function setupReduxDevTools() {
  const composeEnhancers = 
    typeof window !== 'undefined' && 
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 
      ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
          trace: true,
          traceLimit: 25,
        })
      : compose
  
  return composeEnhancers
}

// Zustand DevTools
import { subscribeWithSelector } from 'zustand/middleware'
import { devtools } from 'zustand/middleware'

export const useStore = create(
  devtools(
    subscribeWithSelector((set, get) => ({
      products: [],
      loading: false,
      
      fetchProducts: async () => {
        set({ loading: true }, false, 'fetchProducts/start')
        
        try {
          const products = await api.getProducts()
          set({ products, loading: false }, false, 'fetchProducts/success')
        } catch (error) {
          set({ loading: false }, false, 'fetchProducts/error')
          throw error
        }
      },
    })),
    { name: 'product-store' }
  )
)

// React Query DevTools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

export function QueryDevtools() {
  return (
    <ReactQueryDevtools
      initialIsOpen={false}
      position="bottom-right"
      toggleButtonProps={{
        style: {
          marginLeft: '5px',
          transform: undefined,
          width: '30px',
          height: '30px',
        },
      }}
    />
  )
}

Logging Systems

Structured Logging

// Logger utility
export class Logger {
  private context: string
  
  constructor(context: string) {
    this.context = context
  }
  
  private formatMessage(level: string, message: string, data?: any) {
    return {
      timestamp: new Date().toISOString(),
      level,
      context: this.context,
      message,
      data,
      url: typeof window !== 'undefined' ? window.location.href : undefined,
      userAgent: typeof window !== 'undefined' ? navigator.userAgent : undefined,
    }
  }
  
  debug(message: string, data?: any) {
    if (process.env.NODE_ENV === 'development') {
      console.debug('🐛', this.formatMessage('DEBUG', message, data))
    }
  }
  
  info(message: string, data?: any) {
    console.info('ℹ️', this.formatMessage('INFO', message, data))
  }
  
  warn(message: string, data?: any) {
    console.warn('⚠️', this.formatMessage('WARN', message, data))
  }
  
  error(message: string, error?: Error, data?: any) {
    const logData = {
      ...data,
      error: error ? {
        message: error.message,
        stack: error.stack,
        name: error.name,
      } : undefined,
    }
    
    console.error('❌', this.formatMessage('ERROR', message, logData))
    
    // Send to external logging service in production
    if (process.env.NODE_ENV === 'production') {
      this.sendToLoggingService('ERROR', message, logData)
    }
  }
  
  private async sendToLoggingService(level: string, message: string, data: any) {
    try {
      await fetch('/api/logs', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(this.formatMessage(level, message, data)),
      })
    } catch (error) {
      console.error('Failed to send log to service:', error)
    }
  }
}

// Usage
const logger = new Logger('ProductService')

export async function fetchProducts() {
  logger.debug('Fetching products from API')
  
  try {
    const response = await fetch('/api/products')
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
    }
    
    const products = await response.json()
    logger.info('Successfully fetched products', { count: products.length })
    
    return products
  } catch (error) {
    logger.error('Failed to fetch products', error)
    throw error
  }
}

Error Tracking

// Global error handler
export function setupGlobalErrorHandling() {
  // Unhandled JavaScript errors
  window.addEventListener('error', (event) => {
    const logger = new Logger('GlobalErrorHandler')
    logger.error('Unhandled JavaScript error', event.error, {
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
    })
  })
  
  // Unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    const logger = new Logger('GlobalErrorHandler')
    logger.error('Unhandled promise rejection', undefined, {
      reason: event.reason,
    })
  })
  
  // React error boundaries
  const originalConsoleError = console.error
  console.error = function(...args) {
    const logger = new Logger('React')
    
    if (args[0]?.includes?.('React') || args[0]?.includes?.('component')) {
      logger.error('React error detected', undefined, { args })
    }
    
    originalConsoleError.apply(console, args)
  }
}

// Error boundary component
export class ErrorBoundary extends React.Component<
  { children: React.ReactNode; fallback?: React.ComponentType<{ error: Error }> },
  { hasError: boolean; error?: Error }
> {
  private logger = new Logger('ErrorBoundary')
  
  constructor(props: any) {
    super(props)
    this.state = { hasError: false }
  }
  
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }
  
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    this.logger.error('React component error', error, {
      componentStack: errorInfo.componentStack,
      errorBoundary: this.constructor.name,
    })
  }
  
  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || DefaultErrorFallback
      return <FallbackComponent error={this.state.error!} />
    }
    
    return this.props.children
  }
}

Database Debugging

Query Analysis

// Database query debugging
export function createDebugSupabaseClient() {
  const supabase = createClient(url, key)
  
  // Intercept and log all queries
  const originalFrom = supabase.from
  supabase.from = function(table: string) {
    const query = originalFrom.call(this, table)
    const logger = new Logger('Supabase')
    
    // Override select method
    const originalSelect = query.select
    query.select = function(...args: any[]) {
      logger.debug(`SELECT from ${table}`, { columns: args })
      return originalSelect.apply(this, args)
    }
    
    // Override insert method
    const originalInsert = query.insert
    query.insert = function(data: any) {
      logger.debug(`INSERT into ${table}`, { data })
      return originalInsert.call(this, data)
    }
    
    // Override update method
    const originalUpdate = query.update
    query.update = function(data: any) {
      logger.debug(`UPDATE ${table}`, { data })
      return originalUpdate.call(this, data)
    }
    
    // Override delete method
    const originalDelete = query.delete
    query.delete = function() {
      logger.debug(`DELETE from ${table}`)
      return originalDelete.call(this)
    }
    
    return query
  }
  
  return supabase
}

// SQL query performance monitoring
export async function analyzeQuery(query: string) {
  const startTime = performance.now()
  
  try {
    const { data, error } = await supabase.rpc('explain_query', { query })
    const endTime = performance.now()
    
    console.group(`🔍 Query Analysis (${(endTime - startTime).toFixed(2)}ms)`)
    console.log('Query:', query)
    console.log('Execution Plan:', data)
    console.groupEnd()
    
    return { data, duration: endTime - startTime }
  } catch (error) {
    console.error('Query analysis failed:', error)
    throw error
  }
}

Monitoring and Alerts

Real-time Monitoring

// Performance monitoring
export class PerformanceMonitor {
  private metrics: Map<string, number[]> = new Map()
  private observers: PerformanceObserver[] = []
  
  start() {
    // Monitor long tasks
    if ('PerformanceObserver' in window) {
      const longTaskObserver = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.duration > 50) { // Tasks longer than 50ms
            console.warn('🐌 Long task detected:', {
              duration: entry.duration,
              startTime: entry.startTime,
            })
          }
        }
      })
      
      longTaskObserver.observe({ entryTypes: ['longtask'] })
      this.observers.push(longTaskObserver)
    }
    
    // Monitor memory usage
    if ('memory' in performance) {
      setInterval(() => {
        const memory = (performance as any).memory
        const usage = {
          used: Math.round(memory.usedJSHeapSize / 1048576), // MB
          total: Math.round(memory.totalJSHeapSize / 1048576), // MB
          limit: Math.round(memory.jsHeapSizeLimit / 1048576), // MB
        }
        
        if (usage.used > usage.limit * 0.9) {
          console.warn('🚨 High memory usage:', usage)
        }
        
        this.recordMetric('memory-usage', usage.used)
      }, 5000)
    }
  }
  
  recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    const values = this.metrics.get(name)!
    values.push(value)
    
    // Keep only last 100 values
    if (values.length > 100) {
      values.shift()
    }
  }
  
  getMetrics() {
    const result: Record<string, any> = {}
    
    for (const [name, values] of this.metrics.entries()) {
      const sum = values.reduce((a, b) => a + b, 0)
      result[name] = {
        current: values[values.length - 1],
        average: sum / values.length,
        min: Math.min(...values),
        max: Math.max(...values),
        count: values.length,
      }
    }
    
    return result
  }
  
  stop() {
    this.observers.forEach(observer => observer.disconnect())
    this.observers = []
  }
}

// Usage
const monitor = new PerformanceMonitor()
monitor.start()

// View metrics in console
setTimeout(() => {
  console.table(monitor.getMetrics())
}, 10000)

Debugging Best Practices

Code Instrumentation

// Debugging decorators
export function debugMethod(target: any, propertyName: string, descriptor: PropertyDescriptor) {
  const method = descriptor.value
  
  descriptor.value = function(...args: any[]) {
    const logger = new Logger(target.constructor.name)
    
    logger.debug(`Calling ${propertyName}`, { args })
    const startTime = performance.now()
    
    try {
      const result = method.apply(this, args)
      
      // Handle async methods
      if (result instanceof Promise) {
        return result
          .then(value => {
            const duration = performance.now() - startTime
            logger.debug(`${propertyName} completed`, { duration, result: value })
            return value
          })
          .catch(error => {
            const duration = performance.now() - startTime
            logger.error(`${propertyName} failed`, error, { duration, args })
            throw error
          })
      }
      
      const duration = performance.now() - startTime
      logger.debug(`${propertyName} completed`, { duration, result })
      return result
    } catch (error) {
      const duration = performance.now() - startTime
      logger.error(`${propertyName} failed`, error, { duration, args })
      throw error
    }
  }
  
  return descriptor
}

// Usage
export class ProductService {
  @debugMethod
  async getProducts(filters?: any) {
    // Method implementation
  }
  
  @debugMethod
  async createProduct(data: any) {
    // Method implementation
  }
}

Conditional Debugging

// Environment-based debugging
export const debug = {
  enabled: process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true',
  
  log: (...args: any[]) => {
    if (debug.enabled) console.log(...args)
  },
  
  group: (label: string) => {
    if (debug.enabled) console.group(label)
  },
  
  groupEnd: () => {
    if (debug.enabled) console.groupEnd()
  },
  
  time: (label: string) => {
    if (debug.enabled) console.time(label)
  },
  
  timeEnd: (label: string) => {
    if (debug.enabled) console.timeEnd(label)
  },
}

// Feature flag debugging
export function useDebugMode() {
  const [debugMode, setDebugMode] = useState(false)
  
  useEffect(() => {
    // Check for debug query parameter
    const params = new URLSearchParams(window.location.search)
    setDebugMode(params.get('debug') === 'true')
    
    // Listen for keyboard shortcut (Ctrl+Shift+D)
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.ctrlKey && event.shiftKey && event.key === 'D') {
        setDebugMode(prev => !prev)
      }
    }
    
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [])
  
  return debugMode
}

For advanced debugging techniques and tools, refer to the Chrome DevTools Documentation and React DevTools Guide.