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:
@@ -147,23 +147,82 @@ app.get("/api/health", async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const couchdbStatus = await couchdbService.checkConnection();
|
const couchdbStatus = await couchdbService.checkConnection();
|
||||||
|
|
||||||
res.status(200).json({
|
// Check Socket.IO status
|
||||||
status: "healthy",
|
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(),
|
timestamp: new Date().toISOString(),
|
||||||
uptime: process.uptime(),
|
uptime: process.uptime(),
|
||||||
|
services: {
|
||||||
couchdb: couchdbStatus ? "connected" : "disconnected",
|
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) {
|
} catch (error) {
|
||||||
res.status(503).json({
|
res.status(503).json({
|
||||||
status: "unhealthy",
|
status: "unhealthy",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
uptime: process.uptime(),
|
uptime: process.uptime(),
|
||||||
|
services: {
|
||||||
couchdb: "disconnected",
|
couchdb: "disconnected",
|
||||||
|
socketIO: "unknown"
|
||||||
|
},
|
||||||
error: error.message,
|
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
|
// Routes
|
||||||
app.use("/api/auth", authRoutes);
|
app.use("/api/auth", authRoutes);
|
||||||
app.use("/api/streets", streetRoutes);
|
app.use("/api/streets", streetRoutes);
|
||||||
|
|||||||
Reference in New Issue
Block a user