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
- Navigate to
/admin/errors(requires admin role) - Filter by severity, source, date range, or resolution status
- Click on an error to see full details including:
- Stack trace
- Browser environment
- User context
- Request details
- Mark errors as resolved with notes
- Export errors to CSV
Best Practices
-
Use decorators for views: All API viewsets should have
@capture_errors -
Use context managers for critical operations: Payments, data migrations, bulk operations
-
Use
tryCatchfor async code: Clean, Go-style error handling -
Set appropriate severity:
critical: Database errors, payment failures, data losshigh: Unexpected runtime errors, 5xx responsesmedium: Validation errors, user input issueslow: Warnings, graceful degradation
-
Include entity context: Always provide
entity_typeandentity_idwhen operating on specific records -
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
MIDDLEWAREincludesErrorCaptureMiddleware - Ensure the
ApplicationErrormodel is migrated
Frontend errors not reporting?
- Check browser console for API call failures
- Verify
.envhas correctNUXT_PUBLIC_API_BASE(port 8000) - Check network tab for
/api/v1/errors/report/requests