Front-end Engineering Lab

Logging Strategies (Structured Logging)

Implement effective logging for debugging and monitoring

Structured logging makes logs searchable, filterable, and actionable. Essential for debugging production issues.

Structured Logging

// ❌ BAD: Unstructured logs
console.log('User logged in: john@example.com');

// ✅ GOOD: Structured logs
logger.info('user_logged_in', {
  userId: '123',
  email: 'john@example.com',
  timestamp: Date.now(),
  sessionId: 'abc',
});

Logger Implementation

// utils/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

interface LogEntry {
  level: LogLevel;
  message: string;
  context?: Record<string, any>;
  timestamp: number;
  userId?: string;
  sessionId?: string;
}

class Logger {
  private level: LogLevel = 'info';
  private context: Record<string, any> = {};

  setLevel(level: LogLevel) {
    this.level = level;
  }

  setContext(context: Record<string, any>) {
    this.context = { ...this.context, ...context };
  }

  private log(level: LogLevel, message: string, context?: Record<string, any>) {
    const entry: LogEntry = {
      level,
      message,
      context: { ...this.context, ...context },
      timestamp: Date.now(),
    };

    // Console output (dev)
    if (process.env.NODE_ENV === 'development') {
      console[level](message, entry.context);
    }

    // Send to logging service (production)
    if (process.env.NODE_ENV === 'production') {
      this.sendToService(entry);
    }
  }

  debug(message: string, context?: Record<string, any>) {
    if (this.shouldLog('debug')) {
      this.log('debug', message, context);
    }
  }

  info(message: string, context?: Record<string, any>) {
    if (this.shouldLog('info')) {
      this.log('info', message, context);
    }
  }

  warn(message: string, context?: Record<string, any>) {
    if (this.shouldLog('warn')) {
      this.log('warn', message, context);
    }
  }

  error(message: string, context?: Record<string, any>) {
    this.log('error', message, context);
  }

  private shouldLog(level: LogLevel): boolean {
    const levels = ['debug', 'info', 'warn', 'error'];
    return levels.indexOf(level) >= levels.indexOf(this.level);
  }

  private sendToService(entry: LogEntry) {
    // Send to Sentry, DataDog, etc.
    fetch('/api/logs', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(entry),
    }).catch(() => {
      // Silently fail to avoid infinite loops
    });
  }
}

export const logger = new Logger();

Usage Patterns

API Call Logging

export async function apiRequest(url: string, options?: RequestInit) {
  const requestId = crypto.randomUUID();
  
  logger.info('api_request_start', {
    requestId,
    url,
    method: options?.method || 'GET',
  });

  const start = performance.now();

  try {
    const response = await fetch(url, options);
    const duration = performance.now() - start;

    logger.info('api_request_success', {
      requestId,
      url,
      status: response.status,
      duration,
    });

    return response;
  } catch (error) {
    const duration = performance.now() - start;

    logger.error('api_request_error', {
      requestId,
      url,
      error: error instanceof Error ? error.message : 'Unknown error',
      duration,
    });

    throw error;
  }
}

User Action Logging

export function useActionLogger() {
  const logAction = (action: string, context?: Record<string, any>) => {
    logger.info('user_action', {
      action,
      ...context,
      pathname: window.location.pathname,
      referrer: document.referrer,
    });
  };

  return { logAction };
}

// Usage
function Component() {
  const { logAction } = useActionLogger();

  const handleClick = () => {
    logAction('button_clicked', {
      buttonId: 'submit',
      formData: { ... },
    });
  };

  return <button onClick={handleClick}>Submit</button>;
}

Error Logging

// Global error handler
window.addEventListener('error', (event) => {
  logger.error('unhandled_error', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error?.stack,
  });
});

// Promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
  logger.error('unhandled_rejection', {
    reason: event.reason,
    promise: event.promise,
  });
});

// React Error Boundary
class ErrorBoundary extends React.Component {
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    logger.error('react_error', {
      error: error.toString(),
      componentStack: errorInfo.componentStack,
    });
  }
}

Performance Logging

export function logPerformance() {
  // Core Web Vitals
  onLCP((metric) => {
    logger.info('metric_lcp', { value: metric.value });
  });

  onFID((metric) => {
    logger.info('metric_fid', { value: metric.value });
  });

  onCLS((metric) => {
    logger.info('metric_cls', { value: metric.value });
  });

  // Custom timing
  const navEntry = performance.getEntriesByType('navigation')[0] as any;
  
  logger.info('page_timing', {
    dns: navEntry.domainLookupEnd - navEntry.domainLookupStart,
    tcp: navEntry.connectEnd - navEntry.connectStart,
    ttfb: navEntry.responseStart - navEntry.requestStart,
    domLoad: navEntry.domContentLoadedEventEnd - navEntry.fetchStart,
    pageLoad: navEntry.loadEventEnd - navEntry.fetchStart,
  });
}

Log Sampling

// Sample logs in high-traffic apps
class SampledLogger extends Logger {
  private sampleRate = 0.1; // 10%

  setSampleRate(rate: number) {
    this.sampleRate = rate;
  }

  protected shouldSample(): boolean {
    return Math.random() < this.sampleRate;
  }

  info(message: string, context?: Record<string, any>) {
    if (this.shouldSample()) {
      super.info(message, context);
    }
  }

  // Always log errors
  error(message: string, context?: Record<string, any>) {
    super.error(message, context);
  }
}

Best Practices

  1. Structured format - JSON, searchable
  2. Contextual info - userId, sessionId, route
  3. Log levels - debug, info, warn, error
  4. Sample in production - Don't log everything
  5. Never log PII - Passwords, credit cards
  6. Correlation IDs - Track requests across services
  7. Error stack traces - Full context
  8. Performance metrics - Track slowness
  9. Centralized logging - Single source of truth
  10. Alerts on errors - Be proactive

Structured logging enables effective debugging—log smartly with context and levels!

On this page