7.2 KiB
7.2 KiB
Error Tracking with Fallback System
Overview
This project uses a dual error tracking system:
- Primary: Sentry (cloud-based)
- 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
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
errorTracker.trackMessage('Payment processed successfully', 'info', {
amount: 100,
currency: 'USD'
})
Set User Context
// After login
errorTracker.setUser({
id: user.id,
email: user.email,
name: user.name
})
// On logout
errorTracker.clearUser()
Add Breadcrumbs
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:
{
"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:
{
"success": true,
"logId": "log-456"
}
Backend Implementation Example (Node.js/Express)
// 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
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:
// 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:
- Sentry (if available) - Rich debugging features
- Backend (always) - Your own database for compliance/analysis
Sentry Alternatives
If you want to replace Sentry entirely:
1. LogRocket
npm install logrocket
import LogRocket from 'logrocket'
LogRocket.init('your-app-id')
// Track errors
LogRocket.captureException(error)
2. Rollbar
npm install rollbar
import Rollbar from 'rollbar'
const rollbar = new Rollbar({
accessToken: 'your-token',
environment: 'production'
})
rollbar.error(error)
3. Bugsnag
npm install @bugsnag/js @bugsnag/plugin-react
import Bugsnag from '@bugsnag/js'
import BugsnagPluginReact from '@bugsnag/plugin-react'
Bugsnag.start({
apiKey: 'your-api-key',
plugins: [new BugsnagPluginReact()]
})
4. Self-Hosted GlitchTip
# 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
// 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
private maxQueueSize = 100 // Increase for high-traffic apps
Change Backend Endpoint
await apiClient.post('/custom/error-endpoint', errorLog)
Monitoring Dashboard
Build a simple error dashboard:
// 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
// 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
errorTracker.trackError(error, {
extra: {
action: 'checkout',
step: 'payment',
amount: 100
}
})
3. Set User Context Early
// In your auth flow
useEffect(() => {
if (user) {
errorTracker.setUser({
id: user.id,
email: user.email,
name: user.name
})
}
}, [user])
4. Clean Up on Logout
const handleLogout = () => {
errorTracker.clearUser()
// ... rest of logout
}
Troubleshooting
Errors Not Reaching Backend
- Check network tab for failed requests
- Verify backend endpoint exists
- Check CORS configuration
- Review backend logs
Queue Growing Too Large
- Increase
maxQueueSize - Check backend availability
- Add retry logic with exponential backoff
Duplicate Errors
This is intentional - errors go to both Sentry and backend. To disable:
// Only send to backend if Sentry fails
try {
Sentry.captureException(error)
} catch (sentryError) {
this.queueError(errorLog) // Only fallback
}