# Error Handling Guide This guide outlines the standardized error handling patterns used throughout ThrillWiki to ensure consistent, debuggable, and user-friendly error management. ## Core Principles 1. **All errors must be logged** - Never silently swallow errors 2. **Provide context** - Include relevant metadata for debugging 3. **User-friendly messages** - Show clear, actionable error messages to users 4. **Preserve error chains** - Don't lose original error information 5. **Use structured logging** - Avoid raw `console.*` statements ## When to Use What ### `handleError()` - Application Errors (User-Facing) Use `handleError()` for errors that affect user operations and should be visible in the Admin Panel. **When to use:** - Database operation failures - API call failures - Form submission errors - Authentication/authorization failures - Any error that impacts user workflows **Example:** ```typescript import { handleError } from '@/lib/errorHandler'; import { useAuth } from '@/hooks/useAuth'; try { await supabase.from('parks').insert(parkData); handleSuccess('Park Created', 'Your park has been added successfully'); } catch (error) { handleError(error, { action: 'Create Park', userId: user?.id, metadata: { parkName: parkData.name } }); throw error; // Re-throw for parent error boundaries } ``` **Key features:** - Logs to `request_metadata` table with full context - Shows user-friendly toast with error reference ID - Captures breadcrumbs (last 10 user actions) - Visible in Admin Panel at `/admin/error-monitoring` ### `logger.*` - Development & Debugging Logs Use `logger.*` for information that helps developers debug issues without sending data to the database. **When to use:** - Development debugging information - Performance monitoring - Expected failures that don't need Admin Panel visibility - Component lifecycle events - Non-critical informational messages **Available methods:** ```typescript import { logger } from '@/lib/logger'; // Development only - not logged in production logger.log('Component mounted', { props }); logger.info('User action completed', { action: 'click' }); logger.warn('Deprecated API used', { api: 'oldMethod' }); logger.debug('State updated', { newState }); // Always logged - even in production logger.error('Critical failure', { context }); // Specialized logging logger.performance('ComponentName', durationMs); logger.moderationAction('approve', itemId, durationMs); ``` **Example - Expected periodic failures:** ```typescript // Don't show toast or log to Admin Panel for expected periodic failures try { await supabase.rpc('release_expired_locks'); } catch (error) { logger.debug('Periodic lock release failed', { operation: 'release_expired_locks', error: getErrorMessage(error) }); } ``` ### `toast.*` - User Notifications Use toast notifications directly for informational messages, warnings, or confirmations. **When to use:** - Success confirmations (use `handleSuccess()` helper) - Informational messages - Non-error warnings - User confirmations **Example:** ```typescript import { handleSuccess, handleInfo } from '@/lib/errorHandler'; // Success messages handleSuccess('Changes Saved', 'Your profile has been updated'); // Informational messages handleInfo('Processing', 'Your request is being processed'); // Custom toast for special cases toast.info('Feature Coming Soon', { description: 'This feature will be available next month', duration: 4000 }); ``` ### ❌ `console.*` - NEVER USE DIRECTLY **DO NOT USE** `console.*` statements in application code. They are blocked by ESLint. ```typescript // ❌ WRONG - Will fail ESLint check console.log('User clicked button'); console.error('Database error:', error); // ✅ CORRECT - Use logger or handleError logger.log('User clicked button'); handleError(error, { action: 'Database Operation', userId }); ``` **The only exceptions:** - Inside `src/lib/logger.ts` itself - Edge function logging (use `edgeLogger.*`) - Test files (*.test.ts, *.test.tsx) ## Error Handling Patterns ### Pattern 1: Component/Hook Errors (Most Common) For errors in components or custom hooks that affect user operations: ```typescript import { handleError } from '@/lib/errorHandler'; import { useAuth } from '@/hooks/useAuth'; const MyComponent = () => { const { user } = useAuth(); const handleSubmit = async (data: FormData) => { try { await saveData(data); handleSuccess('Saved', 'Your changes have been saved'); } catch (error) { handleError(error, { action: 'Save Form Data', userId: user?.id, metadata: { formType: 'parkEdit' } }); throw error; // Re-throw for error boundaries } }; }; ``` **Key points:** - Always include descriptive action name - Include userId when available - Add relevant metadata for debugging - Re-throw after handling to let error boundaries catch it ### Pattern 2: TanStack Query Errors For errors within React Query hooks: ```typescript import { useQuery } from '@tanstack/react-query'; import { handleError } from '@/lib/errorHandler'; const { data, error, isLoading } = useQuery({ queryKey: ['parks', parkId], queryFn: async () => { const { data, error } = await supabase .from('parks') .select('*') .eq('id', parkId) .single(); if (error) { handleError(error, { action: 'Fetch Park Details', userId: user?.id, metadata: { parkId } }); throw error; } return data; } }); // Handle error state in UI if (error) { return ; } ``` ### Pattern 3: Expected/Recoverable Errors For operations that may fail expectedly and should be logged but not shown to users: ```typescript import { logger } from '@/lib/logger'; import { getErrorMessage } from '@/lib/errorHandler'; // Background operation that may fail without impacting user const syncCache = async () => { try { await performCacheSync(); } catch (error) { // Log for debugging without user notification logger.warn('Cache sync failed', { operation: 'syncCache', error: getErrorMessage(error) }); // Continue execution - cache sync is non-critical } }; ``` ### Pattern 4: Error Boundaries (Top-Level) React Error Boundaries catch unhandled component errors: ```typescript import { Component, ReactNode } from 'react'; import { handleError } from '@/lib/errorHandler'; class ErrorBoundary extends Component< { children: ReactNode }, { hasError: boolean } > { static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { handleError(error, { action: 'Component Error Boundary', metadata: { componentStack: errorInfo.componentStack } }); } render() { if (this.state.hasError) { return ; } return this.props.children; } } ``` ### Pattern 5: Preserve Error Context in Chains When catching and re-throwing errors, preserve the original error information: ```typescript // ❌ WRONG - Loses original error try { await operation(); } catch (error) { throw new Error('Operation failed'); // Original error lost! } // ❌ WRONG - Silent catch loses context const data = await fetch(url) .then(res => res.json()) .catch(() => ({ message: 'Failed' })); // Error details lost! // ✅ CORRECT - Preserve and log error try { const response = await fetch(url); if (!response.ok) { const errorData = await response.json().catch((parseError) => { logger.warn('Failed to parse error response', { error: getErrorMessage(parseError), status: response.status }); return { message: 'Request failed' }; }); throw new Error(errorData.message); } return await response.json(); } catch (error) { handleError(error, { action: 'Fetch Data', userId: user?.id, metadata: { url } }); throw error; } ``` ## Automatic Breadcrumb Tracking The application automatically tracks breadcrumbs (last 10 user actions) to provide context for errors. ### Automatic Tracking (No Code Needed) 1. **API Calls** - All Supabase operations are tracked automatically via the wrapped client 2. **Navigation** - Route changes are tracked automatically 3. **Mutation Errors** - TanStack Query mutations log failures automatically ### Manual Breadcrumb Tracking Add breadcrumbs for important user actions: ```typescript import { breadcrumb } from '@/lib/errorBreadcrumbs'; // Navigation breadcrumb (usually automatic) breadcrumb.navigation('/parks/123', '/parks'); // User action breadcrumb breadcrumb.userAction('clicked submit', 'ParkEditForm', { parkId: '123' }); // API call breadcrumb (usually automatic via wrapped client) breadcrumb.apiCall('/api/parks', 'POST', 200); // State change breadcrumb breadcrumb.stateChange('filter changed', { filter: 'status=open' }); ``` **When to add manual breadcrumbs:** - Critical user actions (form submissions, deletions) - Important state changes (filter updates, mode switches) - Non-Supabase API calls - Complex user workflows **When NOT to add breadcrumbs:** - Inside loops or frequently called functions - For every render or effect - For trivial state changes - Inside already tracked operations ## Edge Function Error Handling Edge functions use a separate logger to prevent sensitive data exposure: ```typescript import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; Deno.serve(async (req) => { const tracking = startRequest(); try { // Your edge function logic const result = await performOperation(); const duration = endRequest(tracking); edgeLogger.info('Operation completed', { requestId: tracking.requestId, duration }); return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json' } }); } catch (error) { const duration = endRequest(tracking); edgeLogger.error('Operation failed', { requestId: tracking.requestId, error: error.message, duration }); return new Response( JSON.stringify({ error: 'Operation failed', requestId: tracking.requestId }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } }); ``` **Key features:** - Automatic sanitization of sensitive fields - Request correlation IDs - Structured JSON logging - Duration tracking ## Testing Error Handling ### Manual Testing 1. Visit `/test-error-logging` (dev only) 2. Click "Generate Test Error" 3. Check Admin Panel at `/admin/error-monitoring` 4. Verify error appears with: - Full stack trace - Breadcrumbs (including API calls) - Environment context - User information ### Automated Testing ```typescript import { handleError } from '@/lib/errorHandler'; describe('Error Handling', () => { it('should log errors to database', async () => { const mockError = new Error('Test error'); handleError(mockError, { action: 'Test Action', metadata: { test: true } }); // Verify error logged to request_metadata table const { data } = await supabase .from('request_metadata') .select('*') .eq('error_message', 'Test error') .single(); expect(data).toBeDefined(); expect(data.endpoint).toBe('Test Action'); }); }); ``` ## Common Mistakes to Avoid ### ❌ Mistake 1: Silent Error Catching ```typescript // ❌ WRONG try { await operation(); } catch (error) { // Nothing - error disappears! } // ✅ CORRECT try { await operation(); } catch (error) { logger.debug('Expected operation failure', { operation: 'name', error: getErrorMessage(error) }); } ``` ### ❌ Mistake 2: Using console.* Directly ```typescript // ❌ WRONG - Blocked by ESLint console.log('Debug info', data); console.error('Error occurred', error); // ✅ CORRECT logger.log('Debug info', data); handleError(error, { action: 'Operation Name', userId }); ``` ### ❌ Mistake 3: Not Re-throwing After Handling ```typescript // ❌ WRONG - Error doesn't reach error boundary try { await operation(); } catch (error) { handleError(error, { action: 'Operation' }); // Error stops here - error boundary never sees it } // ✅ CORRECT try { await operation(); } catch (error) { handleError(error, { action: 'Operation' }); throw error; // Let error boundary handle UI fallback } ``` ### ❌ Mistake 4: Generic Error Messages ```typescript // ❌ WRONG - No context handleError(error, { action: 'Error' }); // ✅ CORRECT - Descriptive context handleError(error, { action: 'Update Park Opening Hours', userId: user?.id, metadata: { parkId: park.id, parkName: park.name } }); ``` ### ❌ Mistake 5: Losing Error Context ```typescript // ❌ WRONG .catch(() => ({ error: 'Failed' })) // ✅ CORRECT .catch((error) => { logger.warn('Operation failed', { error: getErrorMessage(error) }); return { error: 'Failed' }; }) ``` ## Error Monitoring Dashboard Access the error monitoring dashboard at `/admin/error-monitoring`: **Features:** - Real-time error list with filtering - Search by error ID, message, or user - Full stack traces - Breadcrumb trails showing user actions before error - Environment context (browser, device, network) - Request metadata (endpoint, method, status) **Error ID Lookup:** Visit `/admin/error-lookup` to search for specific errors by their 8-character reference ID shown to users. ## Related Files **Core Error Handling:** - `src/lib/errorHandler.ts` - Main error handling utilities - `src/lib/errorBreadcrumbs.ts` - Breadcrumb tracking system - `src/lib/environmentContext.ts` - Environment data capture - `src/lib/logger.ts` - Structured logging utility - `src/lib/supabaseClient.ts` - Wrapped client with auto-tracking **Admin Tools:** - `src/pages/admin/ErrorMonitoring.tsx` - Error dashboard - `src/pages/admin/ErrorLookup.tsx` - Error ID search - `src/components/admin/ErrorDetailsModal.tsx` - Error details view **Edge Functions:** - `supabase/functions/_shared/logger.ts` - Edge function logger **Database:** - `request_metadata` table - Stores all error logs - `request_breadcrumbs` table - Stores breadcrumb trails - `log_request_metadata` RPC - Logs errors from client ## Summary **Golden Rules:** 1. ✅ Use `handleError()` for user-facing application errors 2. ✅ Use `logger.*` for development debugging and expected failures 3. ✅ Use `toast.*` for success/info notifications 4. ✅ Use `edgeLogger.*` in edge functions 5. ❌ NEVER use `console.*` directly in application code 6. ✅ Always preserve error context when catching 7. ✅ Re-throw errors after handling for error boundaries 8. ✅ Include descriptive action names and metadata 9. ✅ Manual breadcrumbs for critical user actions only 10. ✅ Test error handling in Admin Panel **Quick Reference:** ```typescript // Application error (user-facing) handleError(error, { action: 'Action Name', userId, metadata }); // Debug log (development only) logger.debug('Debug info', { context }); // Expected failure (log but don't show toast) logger.warn('Expected failure', { error: getErrorMessage(error) }); // Success notification handleSuccess('Title', 'Description'); // Edge function error edgeLogger.error('Error message', { requestId, error: error.message }); ```