393 lines
7.2 KiB
Markdown
393 lines
7.2 KiB
Markdown
# Error Tracking with Fallback System
|
|
|
|
## Overview
|
|
|
|
This project uses a **dual error tracking system**:
|
|
1. **Primary**: Sentry (cloud-based)
|
|
2. **Fallback**: Custom backend logging (if Sentry fails)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Error Occurs
|
|
↓
|
|
Try Sentry First
|
|
↓
|
|
If Sentry Fails → Queue for Backend
|
|
↓
|
|
Send to Backend API: POST /api/v1/errors/log
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Basic Error Tracking
|
|
|
|
```typescript
|
|
import { errorTracker } from '@/lib/error-tracker'
|
|
|
|
try {
|
|
await riskyOperation()
|
|
} catch (error) {
|
|
errorTracker.trackError(error, {
|
|
tags: { section: 'payment' },
|
|
extra: { orderId: '123' },
|
|
userId: user.id
|
|
})
|
|
}
|
|
```
|
|
|
|
### Track Messages
|
|
|
|
```typescript
|
|
errorTracker.trackMessage('Payment processed successfully', 'info', {
|
|
amount: 100,
|
|
currency: 'USD'
|
|
})
|
|
```
|
|
|
|
### Set User Context
|
|
|
|
```typescript
|
|
// After login
|
|
errorTracker.setUser({
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.name
|
|
})
|
|
|
|
// On logout
|
|
errorTracker.clearUser()
|
|
```
|
|
|
|
### Add Breadcrumbs
|
|
|
|
```typescript
|
|
errorTracker.addBreadcrumb('navigation', 'User clicked checkout button', 'info')
|
|
```
|
|
|
|
## Backend API Required
|
|
|
|
Your backend needs to implement this endpoint:
|
|
|
|
### POST /api/v1/errors/log
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"message": "Error message",
|
|
"stack": "Error stack trace",
|
|
"url": "https://app.example.com/dashboard",
|
|
"userAgent": "Mozilla/5.0...",
|
|
"timestamp": "2024-02-24T10:30:00.000Z",
|
|
"userId": "user-123",
|
|
"extra": {
|
|
"section": "payment",
|
|
"orderId": "123"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"logId": "log-456"
|
|
}
|
|
```
|
|
|
|
### Backend Implementation Example (Node.js/Express)
|
|
|
|
```javascript
|
|
// routes/errors.js
|
|
router.post('/errors/log', async (req, res) => {
|
|
try {
|
|
const { message, stack, url, userAgent, timestamp, userId, extra } = req.body
|
|
|
|
// Save to database
|
|
await ErrorLog.create({
|
|
message,
|
|
stack,
|
|
url,
|
|
userAgent,
|
|
timestamp: new Date(timestamp),
|
|
userId,
|
|
extra: JSON.stringify(extra)
|
|
})
|
|
|
|
// Optional: Send alert for critical errors
|
|
if (message.includes('payment') || message.includes('auth')) {
|
|
await sendSlackAlert(message, stack)
|
|
}
|
|
|
|
res.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Failed to log error:', error)
|
|
res.status(500).json({ success: false })
|
|
}
|
|
})
|
|
```
|
|
|
|
### Database Schema
|
|
|
|
```sql
|
|
CREATE TABLE error_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
message TEXT NOT NULL,
|
|
stack TEXT,
|
|
url TEXT NOT NULL,
|
|
user_agent TEXT,
|
|
timestamp TIMESTAMP NOT NULL,
|
|
user_id UUID,
|
|
extra JSONB,
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_error_logs_timestamp ON error_logs(timestamp DESC);
|
|
CREATE INDEX idx_error_logs_user_id ON error_logs(user_id);
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### 1. Automatic Global Error Handling
|
|
|
|
All uncaught errors are automatically tracked:
|
|
|
|
```typescript
|
|
// Automatically catches all errors
|
|
window.addEventListener('error', (event) => {
|
|
errorTracker.trackError(new Error(event.message))
|
|
})
|
|
|
|
// Catches unhandled promise rejections
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
errorTracker.trackError(event.reason)
|
|
})
|
|
```
|
|
|
|
### 2. Queue System
|
|
|
|
Errors are queued and sent to backend:
|
|
- Max queue size: 50 errors
|
|
- Automatic retry on failure
|
|
- Prevents memory leaks
|
|
|
|
### 3. Dual Tracking
|
|
|
|
Every error is sent to:
|
|
1. **Sentry** (if available) - Rich debugging features
|
|
2. **Backend** (always) - Your own database for compliance/analysis
|
|
|
|
## Sentry Alternatives
|
|
|
|
If you want to replace Sentry entirely:
|
|
|
|
### 1. LogRocket
|
|
```bash
|
|
npm install logrocket
|
|
```
|
|
|
|
```typescript
|
|
import LogRocket from 'logrocket'
|
|
|
|
LogRocket.init('your-app-id')
|
|
|
|
// Track errors
|
|
LogRocket.captureException(error)
|
|
```
|
|
|
|
### 2. Rollbar
|
|
```bash
|
|
npm install rollbar
|
|
```
|
|
|
|
```typescript
|
|
import Rollbar from 'rollbar'
|
|
|
|
const rollbar = new Rollbar({
|
|
accessToken: 'your-token',
|
|
environment: 'production'
|
|
})
|
|
|
|
rollbar.error(error)
|
|
```
|
|
|
|
### 3. Bugsnag
|
|
```bash
|
|
npm install @bugsnag/js @bugsnag/plugin-react
|
|
```
|
|
|
|
```typescript
|
|
import Bugsnag from '@bugsnag/js'
|
|
import BugsnagPluginReact from '@bugsnag/plugin-react'
|
|
|
|
Bugsnag.start({
|
|
apiKey: 'your-api-key',
|
|
plugins: [new BugsnagPluginReact()]
|
|
})
|
|
```
|
|
|
|
### 4. Self-Hosted GlitchTip
|
|
```bash
|
|
# Docker Compose
|
|
docker-compose up -d
|
|
```
|
|
|
|
Free, open-source, Sentry-compatible API.
|
|
|
|
## Benefits of Fallback System
|
|
|
|
### 1. Reliability
|
|
- Never lose error data if Sentry is down
|
|
- Backend always receives errors
|
|
|
|
### 2. Compliance
|
|
- Keep error logs in your own database
|
|
- Meet data residency requirements
|
|
- Full control over sensitive data
|
|
|
|
### 3. Cost Control
|
|
- Reduce Sentry event count
|
|
- Use backend for high-volume errors
|
|
- Keep Sentry for detailed debugging
|
|
|
|
### 4. Custom Analysis
|
|
- Query errors with SQL
|
|
- Build custom dashboards
|
|
- Integrate with your alerting system
|
|
|
|
## Configuration
|
|
|
|
### Enable/Disable Fallback
|
|
|
|
```typescript
|
|
// src/lib/error-tracker.ts
|
|
|
|
// Disable backend logging (Sentry only)
|
|
const ENABLE_BACKEND_LOGGING = false
|
|
|
|
private queueError(errorLog: ErrorLog) {
|
|
if (!ENABLE_BACKEND_LOGGING) return
|
|
// ... rest of code
|
|
}
|
|
```
|
|
|
|
### Adjust Queue Size
|
|
|
|
```typescript
|
|
private maxQueueSize = 100 // Increase for high-traffic apps
|
|
```
|
|
|
|
### Change Backend Endpoint
|
|
|
|
```typescript
|
|
await apiClient.post('/custom/error-endpoint', errorLog)
|
|
```
|
|
|
|
## Monitoring Dashboard
|
|
|
|
Build a simple error dashboard:
|
|
|
|
```typescript
|
|
// Backend endpoint
|
|
router.get('/errors/stats', async (req, res) => {
|
|
const stats = await db.query(`
|
|
SELECT
|
|
DATE(timestamp) as date,
|
|
COUNT(*) as count,
|
|
COUNT(DISTINCT user_id) as affected_users
|
|
FROM error_logs
|
|
WHERE timestamp > NOW() - INTERVAL '7 days'
|
|
GROUP BY DATE(timestamp)
|
|
ORDER BY date DESC
|
|
`)
|
|
|
|
res.json(stats)
|
|
})
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Don't Log Everything
|
|
```typescript
|
|
// Bad: Logging expected errors
|
|
if (!user) {
|
|
errorTracker.trackError(new Error('User not found'))
|
|
}
|
|
|
|
// Good: Only log unexpected errors
|
|
try {
|
|
await criticalOperation()
|
|
} catch (error) {
|
|
errorTracker.trackError(error)
|
|
}
|
|
```
|
|
|
|
### 2. Add Context
|
|
```typescript
|
|
errorTracker.trackError(error, {
|
|
extra: {
|
|
action: 'checkout',
|
|
step: 'payment',
|
|
amount: 100
|
|
}
|
|
})
|
|
```
|
|
|
|
### 3. Set User Context Early
|
|
```typescript
|
|
// In your auth flow
|
|
useEffect(() => {
|
|
if (user) {
|
|
errorTracker.setUser({
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.name
|
|
})
|
|
}
|
|
}, [user])
|
|
```
|
|
|
|
### 4. Clean Up on Logout
|
|
```typescript
|
|
const handleLogout = () => {
|
|
errorTracker.clearUser()
|
|
// ... rest of logout
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Errors Not Reaching Backend
|
|
|
|
1. Check network tab for failed requests
|
|
2. Verify backend endpoint exists
|
|
3. Check CORS configuration
|
|
4. Review backend logs
|
|
|
|
### Queue Growing Too Large
|
|
|
|
1. Increase `maxQueueSize`
|
|
2. Check backend availability
|
|
3. Add retry logic with exponential backoff
|
|
|
|
### Duplicate Errors
|
|
|
|
This is intentional - errors go to both Sentry and backend. To disable:
|
|
|
|
```typescript
|
|
// Only send to backend if Sentry fails
|
|
try {
|
|
Sentry.captureException(error)
|
|
} catch (sentryError) {
|
|
this.queueError(errorLog) // Only fallback
|
|
}
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [Sentry Documentation](https://docs.sentry.io/)
|
|
- [LogRocket Documentation](https://docs.logrocket.com/)
|
|
- [Rollbar Documentation](https://docs.rollbar.com/)
|
|
- [GlitchTip (Self-hosted)](https://glitchtip.com/)
|
|
|