Files
thrillwiki_django_no_react/docs/ERROR_HANDLING.md

7.7 KiB

Error Handling Guide

This guide covers ThrillWiki's comprehensive error handling system for both the Django backend and Nuxt frontend.

Overview

The error system captures errors from all sources (frontend, backend, API) and displays them in the admin dashboard at /admin/errors.


Quick Start

Backend (Python)

from apps.core.utils import capture_errors, error_context, capture_and_log

# Option 1: Decorator on functions/views
@capture_errors(severity='high')
def create_park(request, data):
    return ParkService.create(data)  # Errors auto-captured

# Option 2: Context manager for code blocks
with error_context('Processing payment', severity='critical'):
    process_payment()

# Option 3: Manual capture
try:
    risky_operation()
except Exception as e:
    error_id = capture_and_log(e, 'Risky operation failed')
    return Response({'error': f'Failed (ref: {error_id})'})

Frontend (TypeScript)

// Option 1: useErrorBoundary composable
const { wrap, error, isError, retry } = useErrorBoundary({
  componentName: 'RideDetail'
})
const ride = await wrap(() => api.get('/rides/1'), 'Loading ride')

// Option 2: tryCatch utility (Go-style)
const [data, err] = await tryCatch(fetchRide(id), 'Fetching ride')
if (err) return // Error already reported

// Option 3: useReportError (manual)
const { reportError } = useReportError()
try {
  await riskyOperation()
} catch (e) {
  reportError(e, { action: 'Risky operation' })
}

Backend Utilities

@capture_errors Decorator

Automatically captures exceptions from decorated functions.

from apps.core.utils import capture_errors

@capture_errors(
    source='api',        # 'frontend', 'backend', or 'api'
    severity='high',     # 'critical', 'high', 'medium', 'low'
    reraise=True,        # Re-raise after capturing (default True)
    log_errors=True      # Also log to Python logger (default True)
)
def my_view(request):
    # If this raises, error is automatically captured
    return do_something()

Use for: API views, service methods, any function where you want automatic tracking.

error_context Context Manager

Captures errors from a code block with rich context.

from apps.core.utils import error_context

with error_context(
    'Creating ride submission',
    source='backend',
    severity='high',
    request=request,           # Optional: for user/IP context
    entity_type='Ride',        # Optional: what entity
    entity_id=123,             # Optional: which entity
    reraise=True               # Default True
):
    submission = SubmissionService.create(data)

Use for: Specific operations where you want detailed context about what was happening.

capture_and_log Function

One-liner for manual error capture that returns the error ID.

from apps.core.utils import capture_and_log

try:
    result = risky_operation()
except Exception as e:
    error_id = capture_and_log(
        e,
        'Risky operation',
        severity='high',
        request=request,
        entity_type='Park',
        entity_id=42
    )
    return Response({'error': f'Failed (ref: {error_id})'}, status=500)

Use for: When you need the error ID to show to users for support reference.


Frontend Utilities

useErrorBoundary Composable

Component-level error boundary with retry support.

const {
  error,        // Ref<Error | null> - current error
  isError,      // ComputedRef<boolean> - whether error exists
  lastErrorId,  // Ref<string | null> - for support reference
  clear,        // () => void - clear error state
  retry,        // () => Promise<void> - retry last operation
  wrap,         // Wrap async function
  wrapSync      // Wrap sync function
} = useErrorBoundary({
  componentName: 'MyComponent',
  entityType: 'Ride',
  defaultSeverity: 'medium',
  showToast: true,
  onError: (err, id) => console.log('Error:', err)
})

// In template
// <div v-if="isError" class="error">
//   {{ error?.message }}
//   <button @click="retry">Try Again</button>
// </div>

tryCatch Utility

Go-style error handling for one-liners.

import { tryCatch, tryCatchSync } from '~/utils/tryCatch'

// Async
const [ride, error] = await tryCatch(api.get('/rides/1'), 'Loading ride')
if (error) {
  console.log('Failed:', error.message)
  return
}
console.log('Got ride:', ride)

// Sync
const [data, err] = tryCatchSync(() => JSON.parse(input), 'Parsing JSON')

// With options
const [data, err] = await tryCatch(fetchData(), 'Fetching', {
  severity: 'critical',
  silent: true,  // No toast notification
  entityType: 'Ride',
  entityId: 123
})

useReportError Composable

Full-featured error reporting with maximum context.

const { reportError, reportErrorSilent, withErrorReporting } = useReportError()

// Report with toast
await reportError(error, {
  action: 'Saving ride',
  componentName: 'EditRideModal',
  entityType: 'Ride',
  entityId: 42,
  severity: 'high',
  metadata: { custom: 'data' }
})

// Report without toast
await reportErrorSilent(error, { action: 'Background task' })

// Wrap a function
const safeFetch = withErrorReporting(fetchRide, {
  componentName: 'RideDetail',
  entityType: 'Ride'
})

Global Error Plugin

The errorPlugin.client.ts automatically catches:

  • Vue component errors
  • Unhandled promise rejections
  • Global JavaScript errors

These are reported silently to the dashboard without user intervention.


Viewing Errors

  1. Navigate to /admin/errors (requires admin role)
  2. Filter by severity, source, date range, or resolution status
  3. Click on an error to see full details including:
    • Stack trace
    • Browser environment
    • User context
    • Request details
  4. Mark errors as resolved with notes
  5. Export errors to CSV

Best Practices

  1. Use decorators for views: All API viewsets should have @capture_errors

  2. Use context managers for critical operations: Payments, data migrations, bulk operations

  3. Use tryCatch for async code: Clean, Go-style error handling

  4. Set appropriate severity:

    • critical: Database errors, payment failures, data loss
    • high: Unexpected runtime errors, 5xx responses
    • medium: Validation errors, user input issues
    • low: Warnings, graceful degradation
  5. Include entity context: Always provide entity_type and entity_id when operating on specific records

  6. Don't swallow errors: Use reraise=True (default) unless you have a recovery strategy


Integration Patterns

API ViewSet with Decorator

from rest_framework import viewsets
from apps.core.utils import capture_errors

class RideViewSet(viewsets.ModelViewSet):
    @capture_errors(source='api')
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

Vue Component with Error Boundary

<script setup lang="ts">
const { wrap, isError, error, retry } = useErrorBoundary({
  componentName: 'RideCard'
})

const ride = ref(null)

onMounted(async () => {
  ride.value = await wrap(() => api.get(`/rides/${props.id}`), 'Loading ride')
})
</script>

<template>
  <div v-if="isError" class="error-state">
    <p>{{ error?.message }}</p>
    <UButton @click="retry">Retry</UButton>
  </div>
  <div v-else-if="ride">
    <!-- Normal content -->
  </div>
</template>

Troubleshooting

Errors not appearing in dashboard?

  • Check the backend server logs for capture failures
  • Verify MIDDLEWARE includes ErrorCaptureMiddleware
  • Ensure the ApplicationError model is migrated

Frontend errors not reporting?

  • Check browser console for API call failures
  • Verify .env has correct NUXT_PUBLIC_API_BASE (port 8000)
  • Check network tab for /api/v1/errors/report/ requests