Logging Systems

Overview

PadawanForge implements comprehensive logging systems for both client-side and server-side error tracking, performance monitoring, and debugging. These systems provide structured logging, error categorization, and detailed context for troubleshooting.

Architecture

Core Components

  • ClientLogger: Browser-side error tracking and logging
  • ErrorLogger: Server-side structured error logging
  • ApiLogger: API-specific logging utilities
  • Error Categorization: Automatic error classification
  • Context Tracking: Rich error context and metadata

Logging Features

  • Structured Logging: Consistent log format across all systems
  • Error Categorization: Automatic classification of errors
  • Severity Levels: Configurable severity assessment
  • Context Preservation: Rich metadata and context tracking
  • Performance Monitoring: Request timing and performance metrics

Client-Side Logging

ClientLogger Class

import { ClientLogger } from '@/lib/utils/client-logger';

// Get singleton instance
const clientLogger = ClientLogger.getInstance();

// Log different types of errors
clientLogger.logError(new Error('Network request failed'), {
  status: 500,
  endpoint: '/api/players',
  requestId: 'req_123'
});

clientLogger.logWarning('Slow network connection detected', {
  responseTime: 5000,
  endpoint: '/api/game-data'
});

clientLogger.logInfo('User completed tutorial', {
  playerId: 'player_123',
  tutorialStep: 'final'
});

Error Categorization

// Automatic error categorization
const errorCategories = {
  'network': 'Network-related errors (fetch, 522, etc.)',
  'timeout': 'Request timeout and abort errors',
  'parsing': 'JSON parsing and syntax errors',
  'validation': 'Input validation errors',
  'unknown': 'Uncategorized errors'
};

// Severity levels
const severityLevels = {
  'low': 'Minor issues, non-critical',
  'medium': 'Moderate issues, may affect functionality',
  'high': 'Significant issues, affects user experience',
  'critical': 'Critical issues, application may be unusable'
};

Client Error Interface

interface ClientError {
  id: string;                      // Unique error ID
  message: string;                 // Error message
  stack?: string;                  // Stack trace
  context: ClientLogContext;       // Error context
  category: 'network' | 'timeout' | 'parsing' | 'validation' | 'unknown';
  severity: 'low' | 'medium' | 'high' | 'critical';
  metadata?: Record<string, any>;  // Additional metadata
}

interface ClientLogContext {
  timestamp: string;               // Error timestamp
  userAgent: string;               // Browser user agent
  url: string;                     // Current page URL
  sessionId?: string;              // Session identifier
  playerId?: string;               // Player identifier
  environment: 'development' | 'production';
}

Usage Examples

Basic Error Logging

// Log network errors
clientLogger.logError(new Error('Failed to fetch data'), {
  status: 404,
  endpoint: '/api/players/123'
});

// Log timeout errors
clientLogger.logError(new Error('Request timeout'), {
  timeoutMs: 10000,
  endpoint: '/api/game-session'
});

// Log parsing errors
clientLogger.logError(new Error('Invalid JSON response'), {
  responseText: '{"invalid": json}',
  endpoint: '/api/config'
});

Specialized Error Logging

// Log room fetch errors
clientLogger.logRoomFetchError(
  'room_123',
  500,
  'Internal Server Error',
  'Database connection failed',
  '/api/lobby/room_123'
);

// Log network timeouts
clientLogger.logNetworkTimeout(
  'room_123',
  10000,
  '/api/lobby/room_123'
);

// Log JSON parse errors
clientLogger.logJsonParseError(
  'room_123',
  '{"invalid": json}',
  new Error('Unexpected token')
);

Error Statistics and Analytics

// Get recent errors
const recentErrors = clientLogger.getRecentErrors(10);

// Get error statistics
const errorStats = clientLogger.getErrorStats();
console.log(errorStats);
// {
//   total: 25,
//   bySeverity: { low: 5, medium: 10, high: 8, critical: 2 },
//   byCategory: { network: 12, timeout: 5, parsing: 3, validation: 3, unknown: 2 }
// }

// Clear error history
clientLogger.clearErrors();

Server-Side Logging

ErrorLogger Class

import { ErrorLogger } from '@/lib/utils/error-logger';

// Get singleton instance
const errorLogger = ErrorLogger.getInstance();

// Create error context
const context = errorLogger.createContext(request, '/api/players', locals);

// Log structured error
const structuredError = errorLogger.logError(new Error('Database query failed'), context, {
  query: 'SELECT * FROM players',
  params: [playerId],
  duration: 1500
});

Structured Error Interface

interface StructuredError {
  requestId: string;               // Unique request ID
  code: string;                    // Error code
  message: string;                 // Error message
  category: 'database' | 'network' | 'timeout' | 'validation' | 'authorization' | 'system' | 'unknown';
  severity: 'low' | 'medium' | 'high' | 'critical';
  context: ErrorContext;           // Error context
  stack?: string;                  // Stack trace
  details?: Record<string, unknown>; // Additional details
  suggestions?: string[];          // Suggested solutions
}

interface ErrorContext {
  requestId?: string;              // Request identifier
  endpoint: string;                // API endpoint
  method: string;                  // HTTP method
  userAgent?: string;              // User agent
  playerUuid?: string;             // Player UUID
  timestamp: string;               // Error timestamp
  environment: {                   // Environment info
    databaseAvailable: boolean;
    kvAvailable: boolean;
    durableObjectsAvailable: boolean;
  };
  performance: {                   // Performance metrics
    startTime: number;
    endTime?: number;
    duration?: number;
  };
  request?: {                      // Request details
    headers?: Record<string, string>;
    params?: Record<string, unknown>;
    body?: unknown;
  };
  response?: {                     // Response details
    status?: number;
    headers?: Record<string, string>;
    body?: unknown;
  };
}

API Logger

import { apiLogger } from '@/lib/utils/logger';

// Log API events
apiLogger.info('API request started', {
  endpoint: '/api/players',
  method: 'GET',
  playerId: 'player_123'
});

apiLogger.warn('Slow database query detected', {
  query: 'SELECT * FROM players',
  duration: 2500,
  endpoint: '/api/players'
});

apiLogger.error('Database connection failed', new Error('Connection timeout'), {
  endpoint: '/api/players',
  retryCount: 3
});

apiLogger.debug('Processing request parameters', {
  params: { limit: 10, offset: 0 },
  endpoint: '/api/players'
});

Error Categorization

Automatic Categorization

// Categorize errors based on message content
function categorizeError(error: Error): string {
  const message = error.message.toLowerCase();
  
  if (message.includes('database') || message.includes('prepare') || message.includes('sqlite')) {
    return 'database';
  }
  
  if (message.includes('fetch') || message.includes('network') || message.includes('522')) {
    return 'network';
  }
  
  if (message.includes('timeout') || message.includes('abort')) {
    return 'timeout';
  }
  
  if (message.includes('validation') || message.includes('invalid')) {
    return 'validation';
  }
  
  if (message.includes('unauthorized') || message.includes('forbidden')) {
    return 'authorization';
  }
  
  return 'unknown';
}

Severity Assessment

// Assess error severity
function getSeverity(category: string, status?: number): string {
  if (status === 522 || category === 'timeout') return 'critical';
  if (status && status >= 500) return 'high';
  if (category === 'network' || category === 'database') return 'high';
  if (status && status >= 400) return 'medium';
  return 'low';
}

Context Tracking

Request Context

// Create comprehensive request context
function createRequestContext(request: Request, endpoint: string, locals?: any) {
  return {
    requestId: generateRequestId(),
    endpoint,
    method: request.method,
    userAgent: request.headers.get('User-Agent') || 'unknown',
    timestamp: new Date().toISOString(),
    environment: {
      databaseAvailable: Boolean(locals?.runtime?.env?.DB),
      kvAvailable: Boolean(locals?.runtime?.env?.SESSION),
      durableObjectsAvailable: Boolean(locals?.runtime?.env?.CHAT_LOBBY),
    },
    performance: {
      startTime: performance.now(),
    },
    request: {
      headers: Object.fromEntries(request.headers.entries()),
      params: {},
    },
  };
}

Performance Tracking

// Track request performance
function updateContextWithResponse(context: ErrorContext, response: Response, responseBody?: unknown) {
  context.performance.endTime = performance.now();
  context.performance.duration = context.performance.endTime - context.performance.startTime;
  
  context.response = {
    status: response.status,
    headers: Object.fromEntries(response.headers.entries()),
    body: responseBody,
  };
}

Error Recovery and Suggestions

Error Suggestions

// Generate error suggestions
function getSuggestions(error: Error, category: string): string[] {
  const suggestions: string[] = [];
  
  switch (category) {
    case 'database':
      suggestions.push('Check database connection');
      suggestions.push('Verify SQL query syntax');
      suggestions.push('Check database permissions');
      break;
      
    case 'network':
      suggestions.push('Check internet connection');
      suggestions.push('Verify API endpoint');
      suggestions.push('Check firewall settings');
      break;
      
    case 'timeout':
      suggestions.push('Increase timeout settings');
      suggestions.push('Check server load');
      suggestions.push('Optimize query performance');
      break;
      
    case 'validation':
      suggestions.push('Check input format');
      suggestions.push('Verify required fields');
      suggestions.push('Review validation rules');
      break;
      
    case 'authorization':
      suggestions.push('Check authentication token');
      suggestions.push('Verify user permissions');
      suggestions.push('Review access controls');
      break;
  }
  
  return suggestions;
}

Integration Examples

API Route Integration

import { ErrorLogger, apiLogger } from '@/lib/utils/error-logger';

export async function GET(request: Request) {
  const errorLogger = ErrorLogger.getInstance();
  const context = errorLogger.createContext(request, '/api/players');
  
  try {
    apiLogger.info('Processing players request', { context: context.requestId });
    
    const players = await getPlayers();
    const response = new Response(JSON.stringify({ players }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' }
    });
    
    errorLogger.updateContextWithResponse(context, response);
    apiLogger.info('Players request completed', { 
      context: context.requestId,
      duration: context.performance.duration 
    });
    
    return response;
  } catch (error) {
    const structuredError = errorLogger.logError(error as Error, context, {
      operation: 'getPlayers',
      playerCount: 0
    });
    
    apiLogger.error('Players request failed', error as Error, {
      context: context.requestId,
      errorId: structuredError.requestId
    });
    
    return errorLogger.createErrorResponse(structuredError, process.env.NODE_ENV === 'development');
  }
}

Component Error Boundary

import { Component, ErrorInfo, ReactNode } from 'react';
import { ClientLogger } from '@/lib/utils/client-logger';

interface ErrorBoundaryProps {
  children: ReactNode;
  fallback?: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  private clientLogger = ClientLogger.getInstance();

  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.clientLogger.logError(error, {
      componentStack: errorInfo.componentStack,
      componentName: this.constructor.name
    });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>We've logged this error and will investigate.</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Middleware Integration

import { defineMiddleware } from 'astro:middleware';
import { ErrorLogger, apiLogger } from '@/lib/utils/error-logger';

export const onRequest = defineMiddleware(async ({ request, locals, next }) => {
  const errorLogger = ErrorLogger.getInstance();
  const context = errorLogger.createContext(request, new URL(request.url).pathname, locals);
  
  try {
    apiLogger.info('Request started', {
      context: context.requestId,
      endpoint: context.endpoint,
      method: context.method
    });
    
    const response = await next();
    
    errorLogger.updateContextWithResponse(context, response);
    apiLogger.info('Request completed', {
      context: context.requestId,
      status: response.status,
      duration: context.performance.duration
    });
    
    return response;
  } catch (error) {
    const structuredError = errorLogger.logError(error as Error, context);
    
    apiLogger.error('Request failed', error as Error, {
      context: context.requestId,
      errorId: structuredError.requestId
    });
    
    return errorLogger.createErrorResponse(structuredError, process.env.NODE_ENV === 'development');
  }
});

Performance Monitoring

Request Timing

// Track request timing
class PerformanceTracker {
  private timings = new Map<string, number>();

  startTimer(requestId: string): void {
    this.timings.set(requestId, performance.now());
  }

  endTimer(requestId: string): number {
    const startTime = this.timings.get(requestId);
    if (!startTime) return 0;
    
    const duration = performance.now() - startTime;
    this.timings.delete(requestId);
    
    return duration;
  }

  logSlowRequest(requestId: string, duration: number, threshold: number = 1000): void {
    if (duration > threshold) {
      apiLogger.warn('Slow request detected', {
        requestId,
        duration,
        threshold
      });
    }
  }
}

Memory Usage Tracking

// Track memory usage
function logMemoryUsage(requestId: string): void {
  if (typeof performance.memory !== 'undefined') {
    const memory = performance.memory;
    
    apiLogger.debug('Memory usage', {
      requestId,
      usedJSHeapSize: memory.usedJSHeapSize,
      totalJSHeapSize: memory.totalJSHeapSize,
      jsHeapSizeLimit: memory.jsHeapSizeLimit
    });
  }
}

Testing

Logging System Testing

describe('Logging Systems', () => {
  let clientLogger: ClientLogger;
  let errorLogger: ErrorLogger;

  beforeEach(() => {
    clientLogger = ClientLogger.getInstance();
    errorLogger = ErrorLogger.getInstance();
  });

  it('should categorize errors correctly', () => {
    const networkError = new Error('fetch failed');
    const dbError = new Error('database connection failed');
    
    expect(clientLogger.categorizeError(networkError)).toBe('network');
    expect(clientLogger.categorizeError(dbError)).toBe('unknown');
  });

  it('should assess severity correctly', () => {
    expect(clientLogger.getSeverity('network', 500)).toBe('high');
    expect(clientLogger.getSeverity('timeout')).toBe('critical');
    expect(clientLogger.getSeverity('validation', 400)).toBe('medium');
  });

  it('should track error statistics', () => {
    clientLogger.logError(new Error('test error'), { status: 500 });
    
    const stats = clientLogger.getErrorStats();
    expect(stats.total).toBe(1);
    expect(stats.bySeverity.high).toBe(1);
  });
});

This comprehensive logging system provides robust error tracking, performance monitoring, and debugging capabilities across the entire PadawanForge application.

PadawanForge v1.4.1