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.