feat: enhance health check endpoints with Socket.IO monitoring

- Enhanced /api/health endpoint
  - Added Socket.IO status monitoring (engine status, client count, active sockets)
  - Added memory usage metrics (heap used, heap total, RSS)
  - Returns 503 (degraded) if either CouchDB or Socket.IO is down
  - Restructured response with nested 'services' object for better clarity

- Added dedicated /api/health/socketio endpoint
  - Provides detailed Socket.IO connection information
  - Shows connected clients and active sockets count
  - Lists active rooms (event_ and post_ rooms) with member counts
  - Useful for debugging real-time connection issues

Benefits:
- Better observability for Kubernetes health probes
- Can monitor Socket.IO connection health separately from database
- Helps diagnose real-time feature issues
- Memory metrics useful for detecting leaks on resource-constrained Raspberry Pi nodes

Response Format:
GET /api/health
{
  "status": "healthy",
  "timestamp": "...",
  "uptime": 123.45,
  "services": {
    "couchdb": "connected",
    "socketIO": {
      "status": "running",
      "connectedClients": 5,
      "activeSockets": 5
    }
  },
  "memory": { ... }
}

GET /api/health/socketio
{
  "status": "running",
  "connectedClients": 5,
  "activeSockets": 5,
  "rooms": [
    { "name": "event_123", "members": 3 },
    { "name": "post_456", "members": 2 }
  ]
}

🤖 Generated with AI Assistant

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-11-03 13:08:26 -08:00
parent 928d9a65fe
commit a12519aa41

View File

@@ -147,23 +147,82 @@ app.get("/api/health", async (req, res) => {
try {
const couchdbStatus = await couchdbService.checkConnection();
res.status(200).json({
status: "healthy",
// Check Socket.IO status
const socketIOStatus = {
engine: io.engine ? "running" : "stopped",
connectedClients: io.engine ? io.engine.clientsCount : 0,
// Get number of connected sockets
sockets: io.sockets ? io.sockets.sockets.size : 0
};
const isHealthy = couchdbStatus && io.engine;
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? "healthy" : "degraded",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
couchdb: couchdbStatus ? "connected" : "disconnected",
services: {
couchdb: couchdbStatus ? "connected" : "disconnected",
socketIO: {
status: socketIOStatus.engine,
connectedClients: socketIOStatus.connectedClients,
activeSockets: socketIOStatus.sockets
}
},
memory: {
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + " MB",
heapTotal: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + " MB",
rss: Math.round(process.memoryUsage().rss / 1024 / 1024) + " MB"
}
});
} catch (error) {
res.status(503).json({
status: "unhealthy",
timestamp: new Date().toISOString(),
uptime: process.uptime(),
couchdb: "disconnected",
services: {
couchdb: "disconnected",
socketIO: "unknown"
},
error: error.message,
});
}
});
// Detailed Socket.IO health check endpoint
app.get("/api/health/socketio", (req, res) => {
try {
const socketIOInfo = {
status: io.engine ? "running" : "stopped",
connectedClients: io.engine ? io.engine.clientsCount : 0,
activeSockets: io.sockets ? io.sockets.sockets.size : 0,
rooms: [],
timestamp: new Date().toISOString()
};
// Get list of active rooms (excluding auto-generated socket ID rooms)
if (io.sockets && io.sockets.adapter && io.sockets.adapter.rooms) {
const rooms = Array.from(io.sockets.adapter.rooms.keys()).filter(room => {
// Filter out socket ID rooms (they start with socket ID pattern)
return room.startsWith('event_') || room.startsWith('post_');
});
socketIOInfo.rooms = rooms.map(room => {
const roomSize = io.sockets.adapter.rooms.get(room)?.size || 0;
return { name: room, members: roomSize };
});
}
res.status(200).json(socketIOInfo);
} catch (error) {
res.status(500).json({
status: "error",
error: error.message,
timestamp: new Date().toISOString()
});
}
});
// Routes
app.use("/api/auth", authRoutes);
app.use("/api/streets", streetRoutes);