Yaltopia-Ticket-Admin/dev-docs/ERROR_TRACKING_FALLBACK.md

7.2 KiB

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

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:

  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

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

  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:

// Only send to backend if Sentry fails
try {
  Sentry.captureException(error)
} catch (sentryError) {
  this.queueError(errorLog) // Only fallback
}

Resources