mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-02-05 14:35:17 -05:00
309 lines
7.7 KiB
Markdown
309 lines
7.7 KiB
Markdown
# 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)
|
|
|
|
```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)
|
|
|
|
```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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```vue
|
|
<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
|