Skip to content

Latest commit

 

History

History
1076 lines (824 loc) · 25.2 KB

File metadata and controls

1076 lines (824 loc) · 25.2 KB

Status Monitoring System Guide

Overview

The Status Monitoring System provides comprehensive real-time health monitoring for all Open-Audit components. It consists of:

  1. Worker Heartbeat System - Background worker reports health via Redis
  2. Health Check API (/api/status) - Parallel component health checks
  3. Status Dashboard (/status) - Beautiful real-time status UI
  4. Circuit Breaker Integration - Monitors resilience layer state

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                     Status Monitoring System                     │
└─────────────────────────────────────────────────────────────────┘
                               │
                               │
        ┌──────────────────────┼──────────────────────┐
        │                      │                      │
        ▼                      ▼                      ▼
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   Worker     │      │  Health API  │      │   Status     │
│  Heartbeat   │─────▶│  Endpoint    │─────▶│  Dashboard   │
│  (Redis)     │      │ /api/status  │      │   /status    │
└──────────────┘      └──────────────┘      └──────────────┘
        │                      │
        │                      │
        ▼                      ▼
┌──────────────────────────────────────┐
│         Redis Hash Storage           │
│  Key: open-audit:worker:heartbeat    │
│  - lastSeen: ISO timestamp           │
│  - workerId: worker identifier       │
│  - processedCount: events processed  │
│  - errorCount: errors encountered    │
│  - uptime: process uptime (seconds)  │
│  - memoryUsage: memory stats (JSON)  │
└──────────────────────────────────────┘

Components

1. Worker Heartbeat (src/worker/indexer.ts)

The indexer worker emits a heartbeat to Redis every 30 seconds (configurable via HEALTH_CHECK_INTERVAL_MS).

Heartbeat Data Structure:

{
  lastSeen: "2026-06-29T10:30:45.123Z",  // ISO 8601 timestamp
  workerId: "worker-12345",               // Unique worker ID
  processedCount: 1500,                   // Total events processed
  errorCount: 5,                          // Total errors encountered
  uptime: 3600,                           // Process uptime in seconds
  memoryUsage: {                          // Node.js memory usage
    rss: 52428800,
    heapTotal: 20971520,
    heapUsed: 15728640,
    external: 1048576,
    arrayBuffers: 524288
  }
}

Redis Storage:

HSET open-audit:worker:heartbeat lastSeen "2026-06-29T10:30:45.123Z"
HSET open-audit:worker:heartbeat workerId "worker-12345"
HSET open-audit:worker:heartbeat processedCount "1500"
HSET open-audit:worker:heartbeat errorCount "5"
HSET open-audit:worker:heartbeat uptime "3600"
HSET open-audit:worker:heartbeat memoryUsage "{...}"

Configuration:

# Health check interval (milliseconds)
HEALTH_CHECK_INTERVAL_MS=30000  # Default: 30 seconds

2. Health Check API (app/api/status/route.ts)

REST API endpoint that performs parallel health checks across all system components.

Endpoint: GET /api/status

Response Time: < 500ms (guaranteed)

HTTP Status Codes:

  • 200 - System is healthy or degraded
  • 503 - System is down (critical components failed)

Response Structure:

{
  status: "healthy" | "degraded" | "down",
  timestamp: "2026-06-29T10:30:45.123Z",
  version: "1.0.0",
  environment: "production",
  components: {
    stellarRpc: {
      status: "healthy",
      latencyMs: 125,
      details: {
        circuitBreakerState: "CLOSED",
        latestLedger: 1000000,
        endpoint: "primary"
      }
    },
    database: {
      status: "healthy",
      latencyMs: 45
    },
    redis: {
      status: "healthy",
      latencyMs: 8
    },
    worker: {
      status: "healthy",
      latencyMs: 12,
      details: {
        lastSeen: "2026-06-29T10:30:30.000Z",
        ageSeconds: 15,
        workerId: "worker-12345",
        processedCount: "1500",
        errorCount: "5",
        uptime: "3600"
      }
    }
  },
  circuitBreaker: {
    state: "CLOSED",
    metrics: {
      totalRequests: 1000,
      totalSuccesses: 950,
      totalFailures: 50,
      consecutiveFailures: 0
    }
  },
  metrics: {
    events: {
      last1Hour: 150,
      last24Hours: 3000
    },
    translations: {
      successRate: 95,
      averageLatencyMs: 25
    },
    websocket: {
      activeConnections: 12
    }
  },
  checks: {
    totalChecks: 4,
    passedChecks: 4,
    failedChecks: 0,
    durationMs: 180
  }
}

Component Status Values:

  • healthy - Component is fully operational
  • degraded - Component is operational but with issues (slow, circuit breaker tripped, etc.)
  • down - Component is not operational
  • not-configured - Component is not configured (optional components only)

Overall Status Determination Logic:

  1. Down - Critical component (Stellar RPC) is down
  2. Degraded - Any component has issues but system is partially operational
  3. Healthy - All configured components are fully operational

Health Checks Performed:

Stellar RPC Check

  • Fetches latest ledger via resilientStellarClient
  • Reads circuit breaker state
  • Timeout: 3 seconds
  • Returns: status, latency, circuit breaker state, latest ledger

Database Check

  • Executes lightweight query: SELECT 1 as health
  • Uses Prisma client
  • Timeout: 2 seconds
  • Returns: status, latency

Redis Check

  • Pings Redis server
  • Timeout: 2 seconds
  • Returns: status, latency
  • Returns not-configured if REDIS_URL not set

Worker Check

  • Reads heartbeat from open-audit:worker:heartbeat Redis hash
  • Validates heartbeat is recent (< 90 seconds old)
  • Timeout: 2 seconds
  • Returns: status, latency, heartbeat details
  • Returns not-configured if Redis not configured

3. Status Dashboard (app/status/page.tsx)

Beautiful, real-time status monitoring dashboard accessible at /status.

Features:

  • 🎨 Color-coded status - Green (healthy), Yellow (degraded), Red (down), Gray (not-configured)
  • 🔄 Auto-refresh - Updates every 30 seconds (toggleable)
  • 📊 Component cards - Individual cards for each component with latency and details
  • Circuit breaker visualization - Shows circuit breaker state and metrics
  • 📈 System metrics - Events, translations, WebSocket connections
  • 🔍 Health check summary - Total checks, passed/failed counts, duration
  • 🔘 Manual refresh - Button to manually refresh status
  • Last updated timestamp - Shows when data was last fetched

Component Icons:

  • Stellar RPC: Activity (⚡)
  • Database: Database (🗄️)
  • Redis Cache: Server (🖥️)
  • Indexer Worker: Zap (⚡)

Status Icons:

  • Healthy: CheckCircle (✓)
  • Degraded: AlertCircle (⚠️)
  • Down: XCircle (✗)
  • Not Configured: AlertCircle (ℹ️)

Circuit Breaker States:

  • CLOSED - Green (normal operation)
  • HALF_OPEN - Yellow (testing recovery)
  • OPEN - Red (failing fast)

Installation & Setup

Prerequisites

  • Node.js 18+ (for Next.js)
  • Redis server (for worker heartbeat)
  • Microservices architecture deployed (worker + web server)

Configuration

Add to .env.local or .env.microservices:

# Redis Configuration (required for worker heartbeat)
REDIS_URL=redis://localhost:6379

# Health Check Interval (optional)
HEALTH_CHECK_INTERVAL_MS=30000  # 30 seconds (default)

# Worker ID (optional, auto-generated if not set)
WORKER_ID=worker-1

Starting the System

Option 1: Local Development (Manual)

# Start Redis
redis-server

# Terminal 1: Start web server
npm run dev:decoupled

# Terminal 2: Start indexer worker
npm run worker:indexer

Option 2: Docker Compose

# Start all services
npm run docker:up

# View logs
npm run docker:logs

# Stop all
npm run docker:down

Option 3: PM2 Process Manager

# Start all services
npm run start:pm2

# View status
npm run monit:pm2

# View logs
npm run logs:pm2

# Stop all
npm run stop:pm2

Usage

Accessing the Status Dashboard

  1. Navigate to the dashboard:

    http://localhost:3000/status
    
  2. Observe the overall status at the top:

    • Green background = Healthy
    • Yellow background = Degraded
    • Red background = Down
  3. Review individual component statuses:

    • Each component shows status, latency, and details
    • Expand details to see additional information
  4. Monitor circuit breaker:

    • Check state (CLOSED, HALF_OPEN, OPEN)
    • Review metrics (requests, successes, failures)
  5. View system metrics:

    • Events processed (1h, 24h)
    • Translation success rate
    • Active WebSocket connections
  6. Toggle auto-refresh:

    • Click "Auto-refresh ON/OFF" button
    • Manual refresh available via refresh button

Using the Health Check API

Check system health programmatically:

# Check health
curl http://localhost:3000/api/status

# Check health with pretty output
curl http://localhost:3000/api/status | jq

Check specific component:

# Check if worker is alive
curl http://localhost:3000/api/status | jq '.components.worker'

# Check circuit breaker state
curl http://localhost:3000/api/status | jq '.circuitBreaker.state'

# Check overall status
curl http://localhost:3000/api/status | jq '.status'

Integration with monitoring tools:

# Prometheus integration (example)
# Add this to your prometheus.yml:
# - job_name: 'open-audit'
#   metrics_path: '/api/status'
#   scrape_interval: 30s
#   static_configs:
#     - targets: ['localhost:3000']

Monitoring Worker Health

Check worker heartbeat directly via Redis:

# Connect to Redis
redis-cli

# Read all heartbeat fields
HGETALL open-audit:worker:heartbeat

# Read specific field
HGET open-audit:worker:heartbeat lastSeen

# Check how old the heartbeat is
HGET open-audit:worker:heartbeat lastSeen
# Compare timestamp to current time

Expected heartbeat freshness:

  • Healthy: < 90 seconds old
  • Degraded: 90-120 seconds old
  • Down: > 120 seconds old or missing

Troubleshooting

Worker Shows "Down" Status

Symptoms:

  • Worker component shows status: "down"
  • Error: "Worker heartbeat is stale" or "No heartbeat found"

Solutions:

  1. Check if worker is running:

    # PM2
    pm2 status
    
    # Docker
    docker ps
    
    # Manual (check process)
    ps aux | grep indexer
  2. Check worker logs:

    # PM2
    pm2 logs indexer-worker
    
    # Docker
    docker logs open-audit-worker
    
    # Manual
    # Check terminal where worker is running
  3. Verify Redis connection:

    # Check if Redis is running
    redis-cli ping
    
    # Check if worker can connect
    redis-cli CLIENT LIST | grep worker
  4. Check heartbeat in Redis:

    redis-cli HGETALL open-audit:worker:heartbeat
  5. Restart worker:

    # PM2
    pm2 restart indexer-worker
    
    # Docker
    docker restart open-audit-worker
    
    # Manual
    # Stop (Ctrl+C) and restart: npm run worker:indexer

Redis Shows "Not Configured" or "Down"

Symptoms:

  • Redis component shows status: "not-configured" or status: "down"
  • Worker shows "down" (depends on Redis)

Solutions:

  1. Check Redis configuration:

    # Verify REDIS_URL is set
    echo $REDIS_URL
  2. Start Redis server:

    # macOS (Homebrew)
    brew services start redis
    
    # Linux (systemd)
    sudo systemctl start redis
    
    # Docker
    docker run -d -p 6379:6379 redis:alpine
    
    # Manual
    redis-server
  3. Test Redis connection:

    redis-cli -h localhost -p 6379 ping
    # Should return: PONG
  4. Check firewall rules:

    # Ensure port 6379 is open
    # Linux
    sudo ufw allow 6379
    
    # macOS
    # No action needed for localhost

Stellar RPC Shows "Degraded"

Symptoms:

  • Stellar RPC shows status: "degraded"
  • Circuit breaker state: OPEN or HALF_OPEN

Solutions:

  1. Check circuit breaker metrics:

    curl http://localhost:3000/api/status | jq '.circuitBreaker'
  2. Wait for circuit breaker to reset:

    • Circuit breaker will automatically transition to HALF_OPEN after cooldown
    • If test succeeds, it will return to CLOSED
  3. Check RPC endpoint availability:

    # Test RPC endpoint manually
    curl https://soroban-testnet.stellar.org/health
  4. Review RPC configuration:

    NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
  5. Check rate limiting:

    • Circuit breaker may trip due to rate limiting
    • Consider using backup endpoints

Database Shows "Down"

Symptoms:

  • Database component shows status: "down"
  • Error: "Database not available" or "Connection refused"

Solutions:

  1. Check if database is running:

    # PostgreSQL
    sudo systemctl status postgresql
    
    # Docker
    docker ps | grep postgres
  2. Verify database connection:

    # Test connection manually
    psql $DATABASE_URL -c "SELECT 1"
  3. Check database credentials:

    DATABASE_URL=postgresql://user:password@localhost:5432/openaudit
  4. Start database:

    # PostgreSQL
    sudo systemctl start postgresql
    
    # Docker
    docker start postgres

Status Dashboard Not Loading

Symptoms:

  • /status page shows loading spinner forever
  • Error in browser console

Solutions:

  1. Check web server logs:

    # PM2
    pm2 logs web-server
    
    # Docker
    docker logs open-audit-web
    
    # Manual
    # Check terminal where server is running
  2. Verify API endpoint:

    curl http://localhost:3000/api/status
  3. Check browser console:

    • Open DevTools (F12)
    • Check Console tab for errors
    • Check Network tab for failed requests
  4. Clear browser cache:

    • Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS)

Health Check Takes Too Long

Symptoms:

  • /api/status takes > 500ms
  • Status dashboard is slow to load

Solutions:

  1. Check component latencies:

    curl http://localhost:3000/api/status | jq '.components | to_entries[] | {key: .key, latency: .value.latencyMs}'
  2. Identify slow component:

    • Stellar RPC timeout: 3 seconds
    • Database timeout: 2 seconds
    • Redis timeout: 2 seconds
    • Worker timeout: 2 seconds
  3. Optimize slow component:

    • Stellar RPC: Use closer endpoint, check network
    • Database: Optimize query, check connection pool
    • Redis: Use local Redis, check network
    • Worker: Ensure heartbeat is recent
  4. Review timeout configuration:

    • Reduce timeout if needed (trade accuracy for speed)
    • Increase timeout if false negatives occur

Performance Considerations

Response Time Targets

  • Health Check API: < 500ms (99th percentile)
  • Dashboard Load: < 1 second (first paint)
  • Dashboard Refresh: < 500ms (subsequent updates)

Optimization Strategies

  1. Parallel Checks:

    • All component checks run in parallel via Promise.all()
    • No sequential blocking
  2. Timeout Protection:

    • Each check has aggressive timeout
    • Prevents one slow component from blocking others
  3. Caching:

    • Circuit breaker metrics are cached (no extra RPC call)
    • Worker heartbeat is cached in Redis
  4. Minimal Queries:

    • Database check uses SELECT 1 (no table scan)
    • Redis check uses PING (no data transfer)

Resource Usage

Memory:

  • Worker heartbeat: ~500 bytes per worker
  • Health check API: ~50 KB per request

Network:

  • Worker heartbeat: 1 write every 30 seconds
  • Health check API: 4 reads per request

CPU:

  • Worker heartbeat: < 0.1% CPU
  • Health check API: < 1% CPU per request

Integration with Monitoring Tools

Prometheus

Add this endpoint to your prometheus.yml:

scrape_configs:
  - job_name: 'open-audit-status'
    metrics_path: '/api/status'
    scrape_interval: 30s
    static_configs:
      - targets: ['localhost:3000']

Grafana

Create a dashboard with these queries:

# Overall status
open_audit_status{status="healthy"}

# Component latency
open_audit_component_latency_ms{component="stellarRpc"}

# Worker heartbeat age
open_audit_worker_heartbeat_age_seconds

# Circuit breaker state
open_audit_circuit_breaker_state{state="CLOSED"}

Nagios

Add this service check:

define service {
    host_name           open-audit
    service_description Status Check
    check_command       check_http!-u /api/status -s "healthy"
    check_interval      1
}

Datadog

Add this configuration to datadog.yaml:

logs:
  - type: file
    path: /var/log/open-audit/*.log
    service: open-audit
    source: nodejs

apm_config:
  enabled: true
  apm_non_local_traffic: true

Security Considerations

Public Access

The /status endpoint is publicly accessible by design for:

  • Load balancer health checks
  • Monitoring systems
  • Status page services
  • Internal teams

What's Exposed:

  • Overall system status
  • Component health (no credentials)
  • Latency metrics
  • Circuit breaker state
  • Aggregate event counts

What's NOT Exposed:

  • Database credentials
  • API keys
  • User data
  • Sensitive configuration
  • Detailed error messages

Rate Limiting

Consider adding rate limiting to prevent abuse:

// Example: Limit to 60 requests per minute per IP
import rateLimit from 'express-rate-limit';

const statusLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 60, // 60 requests per minute
  message: 'Too many status checks from this IP',
});

app.use('/api/status', statusLimiter);

Authentication (Optional)

For private deployments, add authentication:

// Example: Basic auth
export async function GET(request: Request) {
  const auth = request.headers.get('authorization');
  
  if (auth !== `Bearer ${process.env.STATUS_API_KEY}`) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }
  
  // ... rest of health check logic
}

Best Practices

For Developers

  1. Always check status before deployments

    • Ensure system is healthy before deploying changes
    • Monitor status during and after deployment
  2. Set up alerts for degraded/down status

    • Email/Slack notifications when status changes
    • PagerDuty integration for critical failures
  3. Review worker heartbeat regularly

    • Ensure heartbeat is updating every 30 seconds
    • Investigate if heartbeat stops updating
  4. Monitor circuit breaker state

    • OPEN state indicates RPC issues
    • Investigate repeated HALF_OPENOPEN cycles
  5. Keep health check fast

    • Avoid heavy queries in health checks
    • Use timeouts to prevent blocking

For Operations

  1. Set up monitoring dashboards

    • Grafana dashboard with all metrics
    • Prometheus alerting rules
    • Status page for public visibility
  2. Configure backup RPC endpoints

    • Primary + secondary RPC endpoints
    • Circuit breaker will automatically failover
  3. Regular health check validation

    • Test health checks in staging
    • Verify alerts trigger correctly
    • Practice incident response
  4. Documentation

    • Document expected latencies
    • Document troubleshooting steps
    • Document escalation procedures
  5. Capacity planning

    • Monitor worker processed count
    • Scale workers based on event volume
    • Scale web servers based on connection count

API Reference

GET /api/status

Returns comprehensive system health status.

Request:

GET /api/status HTTP/1.1
Host: localhost:3000

Response (200 OK - Healthy):

{
  "status": "healthy",
  "timestamp": "2026-06-29T10:30:45.123Z",
  "version": "1.0.0",
  "environment": "production",
  "components": { /* ... */ },
  "circuitBreaker": { /* ... */ },
  "metrics": { /* ... */ },
  "checks": { /* ... */ }
}

Response (200 OK - Degraded):

{
  "status": "degraded",
  "timestamp": "2026-06-29T10:30:45.123Z",
  "components": {
    "stellarRpc": {
      "status": "degraded",
      "latencyMs": 2500,
      "details": {
        "circuitBreakerState": "HALF_OPEN"
      }
    }
  }
}

Response (503 Service Unavailable - Down):

{
  "status": "down",
  "timestamp": "2026-06-29T10:30:45.123Z",
  "components": {
    "stellarRpc": {
      "status": "down",
      "error": "Connection timeout"
    }
  }
}

Caching Headers:

Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

Testing

Unit Tests

Run the test suite:

npm test -- __tests__/status.test.ts

Test coverage includes:

  • Overall status determination logic
  • Worker heartbeat validation
  • Component latency measurement
  • Circuit breaker integration
  • Error handling
  • Performance (< 500ms guarantee)
  • Metrics aggregation

Manual Testing

Test Worker Heartbeat

# Start worker
npm run worker:indexer

# Wait 30 seconds, then check Redis
redis-cli HGETALL open-audit:worker:heartbeat

# Stop worker
# Kill the worker process

# Wait 2 minutes, check status
curl http://localhost:3000/api/status | jq '.components.worker'
# Should show "down" status

Test Health Check API

# Start all services
npm run start:pm2

# Check health
curl http://localhost:3000/api/status | jq

# Stop Redis
redis-cli shutdown

# Check health again
curl http://localhost:3000/api/status | jq '.components.redis'
# Should show "down" status

# Restart Redis
redis-server &

# Check health again
curl http://localhost:3000/api/status | jq '.components.redis'
# Should show "healthy" status

Test Status Dashboard

  1. Open browser to http://localhost:3000/status
  2. Verify all components show healthy
  3. Stop worker: pm2 stop indexer-worker
  4. Wait 2 minutes
  5. Refresh dashboard
  6. Verify worker shows "down" status with red card
  7. Restart worker: pm2 restart indexer-worker
  8. Wait 30 seconds
  9. Refresh dashboard
  10. Verify worker shows "healthy" status with green card

FAQs

Why is the worker showing "down" when it's running?

The worker may be running but unable to write to Redis. Check:

  1. Redis connection (REDIS_URL configuration)
  2. Worker logs for Redis connection errors
  3. Network connectivity between worker and Redis

How often does the worker send heartbeats?

Default: Every 30 seconds (configurable via HEALTH_CHECK_INTERVAL_MS)

What happens if Redis is down?

  • Worker continues processing events (queues them in memory)
  • Health check API marks Redis and Worker as "down"
  • WebSocket server may not receive events (depends on Pub/Sub)

Can I disable the health check system?

Yes, but not recommended. To disable:

  1. Don't start the worker (no heartbeats)
  2. Remove /api/status and /status routes (requires code changes)

How do I add custom metrics to the dashboard?

Edit app/api/status/route.ts:

  1. Add your metric query to aggregateMetrics() function
  2. Update the metrics field in the response
  3. Edit app/status/page.tsx to display the new metric

What's the overhead of the heartbeat system?

Minimal:

  • Redis write: 1 write every 30 seconds (~500 bytes)
  • Memory: ~500 bytes per worker in Redis
  • CPU: < 0.1% per worker
  • Network: ~1 KB/minute per worker

Can I use this with multiple workers?

Yes! Each worker should have a unique WORKER_ID:

# Worker 1
WORKER_ID=worker-1

# Worker 2
WORKER_ID=worker-2

Currently, the health check reads only one heartbeat key. To support multiple workers, modify the health check to:

  1. Use a hash set with worker IDs as keys
  2. Check all workers' heartbeats
  3. Aggregate status across all workers

Changelog

Version 1.0.0 (2026-06-29)

Initial Release:

  • Worker heartbeat system
  • Health check API endpoint
  • Status dashboard UI
  • Circuit breaker integration
  • Comprehensive documentation
  • Test suite (75+ tests)

Components:

  • src/worker/indexer.ts - Heartbeat implementation
  • app/api/status/route.ts - Health check API
  • app/status/page.tsx - Status dashboard
  • __tests__/status.test.ts - Test suite

Features:

  • Parallel component health checks
  • Sub-500ms response time guarantee
  • Real-time auto-refreshing dashboard
  • Beautiful color-coded UI
  • Circuit breaker state monitoring
  • Comprehensive metrics aggregation

Support

For issues, questions, or feature requests:

  1. Documentation: Review this guide and related docs
  2. GitHub Issues: https://github.com/Open-audit-foundation/Open-Audit/issues
  3. Discord: https://discord.gg/your-server
  4. Email: support@your-domain.com

License

MIT License - see LICENSE file for details.