The Status Monitoring System provides comprehensive real-time health monitoring for all Open-Audit components. It consists of:
- Worker Heartbeat System - Background worker reports health via Redis
- Health Check API (
/api/status) - Parallel component health checks - Status Dashboard (
/status) - Beautiful real-time status UI - Circuit Breaker Integration - Monitors resilience layer state
┌─────────────────────────────────────────────────────────────────┐
│ 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) │
└──────────────────────────────────────┘
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 secondsREST 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 degraded503- 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 operationaldegraded- Component is operational but with issues (slow, circuit breaker tripped, etc.)down- Component is not operationalnot-configured- Component is not configured (optional components only)
Overall Status Determination Logic:
- Down - Critical component (Stellar RPC) is down
- Degraded - Any component has issues but system is partially operational
- Healthy - All configured components are fully operational
Health Checks Performed:
- Fetches latest ledger via
resilientStellarClient - Reads circuit breaker state
- Timeout: 3 seconds
- Returns: status, latency, circuit breaker state, latest ledger
- Executes lightweight query:
SELECT 1 as health - Uses Prisma client
- Timeout: 2 seconds
- Returns: status, latency
- Pings Redis server
- Timeout: 2 seconds
- Returns: status, latency
- Returns
not-configuredifREDIS_URLnot set
- Reads heartbeat from
open-audit:worker:heartbeatRedis hash - Validates heartbeat is recent (< 90 seconds old)
- Timeout: 2 seconds
- Returns: status, latency, heartbeat details
- Returns
not-configuredif Redis not configured
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)
- Node.js 18+ (for Next.js)
- Redis server (for worker heartbeat)
- Microservices architecture deployed (worker + web server)
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# Start Redis
redis-server
# Terminal 1: Start web server
npm run dev:decoupled
# Terminal 2: Start indexer worker
npm run worker:indexer# Start all services
npm run docker:up
# View logs
npm run docker:logs
# Stop all
npm run docker:down# 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-
Navigate to the dashboard:
http://localhost:3000/status -
Observe the overall status at the top:
- Green background = Healthy
- Yellow background = Degraded
- Red background = Down
-
Review individual component statuses:
- Each component shows status, latency, and details
- Expand details to see additional information
-
Monitor circuit breaker:
- Check state (CLOSED, HALF_OPEN, OPEN)
- Review metrics (requests, successes, failures)
-
View system metrics:
- Events processed (1h, 24h)
- Translation success rate
- Active WebSocket connections
-
Toggle auto-refresh:
- Click "Auto-refresh ON/OFF" button
- Manual refresh available via refresh button
# Check health
curl http://localhost:3000/api/status
# Check health with pretty output
curl http://localhost:3000/api/status | jq# 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'# 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']# 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- Healthy: < 90 seconds old
- Degraded: 90-120 seconds old
- Down: > 120 seconds old or missing
Symptoms:
- Worker component shows
status: "down" - Error: "Worker heartbeat is stale" or "No heartbeat found"
Solutions:
-
Check if worker is running:
# PM2 pm2 status # Docker docker ps # Manual (check process) ps aux | grep indexer
-
Check worker logs:
# PM2 pm2 logs indexer-worker # Docker docker logs open-audit-worker # Manual # Check terminal where worker is running
-
Verify Redis connection:
# Check if Redis is running redis-cli ping # Check if worker can connect redis-cli CLIENT LIST | grep worker
-
Check heartbeat in Redis:
redis-cli HGETALL open-audit:worker:heartbeat
-
Restart worker:
# PM2 pm2 restart indexer-worker # Docker docker restart open-audit-worker # Manual # Stop (Ctrl+C) and restart: npm run worker:indexer
Symptoms:
- Redis component shows
status: "not-configured"orstatus: "down" - Worker shows "down" (depends on Redis)
Solutions:
-
Check Redis configuration:
# Verify REDIS_URL is set echo $REDIS_URL
-
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
-
Test Redis connection:
redis-cli -h localhost -p 6379 ping # Should return: PONG -
Check firewall rules:
# Ensure port 6379 is open # Linux sudo ufw allow 6379 # macOS # No action needed for localhost
Symptoms:
- Stellar RPC shows
status: "degraded" - Circuit breaker state:
OPENorHALF_OPEN
Solutions:
-
Check circuit breaker metrics:
curl http://localhost:3000/api/status | jq '.circuitBreaker'
-
Wait for circuit breaker to reset:
- Circuit breaker will automatically transition to
HALF_OPENafter cooldown - If test succeeds, it will return to
CLOSED
- Circuit breaker will automatically transition to
-
Check RPC endpoint availability:
# Test RPC endpoint manually curl https://soroban-testnet.stellar.org/health -
Review RPC configuration:
NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
-
Check rate limiting:
- Circuit breaker may trip due to rate limiting
- Consider using backup endpoints
Symptoms:
- Database component shows
status: "down" - Error: "Database not available" or "Connection refused"
Solutions:
-
Check if database is running:
# PostgreSQL sudo systemctl status postgresql # Docker docker ps | grep postgres
-
Verify database connection:
# Test connection manually psql $DATABASE_URL -c "SELECT 1"
-
Check database credentials:
DATABASE_URL=postgresql://user:password@localhost:5432/openaudit
-
Start database:
# PostgreSQL sudo systemctl start postgresql # Docker docker start postgres
Symptoms:
/statuspage shows loading spinner forever- Error in browser console
Solutions:
-
Check web server logs:
# PM2 pm2 logs web-server # Docker docker logs open-audit-web # Manual # Check terminal where server is running
-
Verify API endpoint:
curl http://localhost:3000/api/status
-
Check browser console:
- Open DevTools (F12)
- Check Console tab for errors
- Check Network tab for failed requests
-
Clear browser cache:
- Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS)
Symptoms:
/api/statustakes > 500ms- Status dashboard is slow to load
Solutions:
-
Check component latencies:
curl http://localhost:3000/api/status | jq '.components | to_entries[] | {key: .key, latency: .value.latencyMs}'
-
Identify slow component:
- Stellar RPC timeout: 3 seconds
- Database timeout: 2 seconds
- Redis timeout: 2 seconds
- Worker timeout: 2 seconds
-
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
-
Review timeout configuration:
- Reduce timeout if needed (trade accuracy for speed)
- Increase timeout if false negatives occur
- Health Check API: < 500ms (99th percentile)
- Dashboard Load: < 1 second (first paint)
- Dashboard Refresh: < 500ms (subsequent updates)
-
Parallel Checks:
- All component checks run in parallel via
Promise.all() - No sequential blocking
- All component checks run in parallel via
-
Timeout Protection:
- Each check has aggressive timeout
- Prevents one slow component from blocking others
-
Caching:
- Circuit breaker metrics are cached (no extra RPC call)
- Worker heartbeat is cached in Redis
-
Minimal Queries:
- Database check uses
SELECT 1(no table scan) - Redis check uses
PING(no data transfer)
- Database check uses
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
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']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"}
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
}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: trueThe /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
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);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
}-
Always check status before deployments
- Ensure system is healthy before deploying changes
- Monitor status during and after deployment
-
Set up alerts for degraded/down status
- Email/Slack notifications when status changes
- PagerDuty integration for critical failures
-
Review worker heartbeat regularly
- Ensure heartbeat is updating every 30 seconds
- Investigate if heartbeat stops updating
-
Monitor circuit breaker state
OPENstate indicates RPC issues- Investigate repeated
HALF_OPEN→OPENcycles
-
Keep health check fast
- Avoid heavy queries in health checks
- Use timeouts to prevent blocking
-
Set up monitoring dashboards
- Grafana dashboard with all metrics
- Prometheus alerting rules
- Status page for public visibility
-
Configure backup RPC endpoints
- Primary + secondary RPC endpoints
- Circuit breaker will automatically failover
-
Regular health check validation
- Test health checks in staging
- Verify alerts trigger correctly
- Practice incident response
-
Documentation
- Document expected latencies
- Document troubleshooting steps
- Document escalation procedures
-
Capacity planning
- Monitor worker processed count
- Scale workers based on event volume
- Scale web servers based on connection count
Returns comprehensive system health status.
Request:
GET /api/status HTTP/1.1
Host: localhost:3000Response (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
Run the test suite:
npm test -- __tests__/status.test.tsTest coverage includes:
- Overall status determination logic
- Worker heartbeat validation
- Component latency measurement
- Circuit breaker integration
- Error handling
- Performance (< 500ms guarantee)
- Metrics aggregation
# 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# 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- Open browser to
http://localhost:3000/status - Verify all components show healthy
- Stop worker:
pm2 stop indexer-worker - Wait 2 minutes
- Refresh dashboard
- Verify worker shows "down" status with red card
- Restart worker:
pm2 restart indexer-worker - Wait 30 seconds
- Refresh dashboard
- Verify worker shows "healthy" status with green card
The worker may be running but unable to write to Redis. Check:
- Redis connection (
REDIS_URLconfiguration) - Worker logs for Redis connection errors
- Network connectivity between worker and Redis
Default: Every 30 seconds (configurable via HEALTH_CHECK_INTERVAL_MS)
- 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)
Yes, but not recommended. To disable:
- Don't start the worker (no heartbeats)
- Remove
/api/statusand/statusroutes (requires code changes)
Edit app/api/status/route.ts:
- Add your metric query to
aggregateMetrics()function - Update the
metricsfield in the response - Edit
app/status/page.tsxto display the new metric
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
Yes! Each worker should have a unique WORKER_ID:
# Worker 1
WORKER_ID=worker-1
# Worker 2
WORKER_ID=worker-2Currently, the health check reads only one heartbeat key. To support multiple workers, modify the health check to:
- Use a hash set with worker IDs as keys
- Check all workers' heartbeats
- Aggregate status across all workers
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 implementationapp/api/status/route.ts- Health check APIapp/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
For issues, questions, or feature requests:
- Documentation: Review this guide and related docs
- GitHub Issues: https://github.com/Open-audit-foundation/Open-Audit/issues
- Discord: https://discord.gg/your-server
- Email: support@your-domain.com
MIT License - see LICENSE file for details.