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.