317 lines
9.3 KiB
JavaScript
317 lines
9.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Simple Health Check Controller
|
|
* Provides basic health monitoring for partner integration
|
|
*/
|
|
|
|
const { PartnerSystemUser } = require('../model/partner');
|
|
const partnerSyncService = require('../services/partner_sync_service');
|
|
const mongoose = require('mongoose');
|
|
const { SyncStatus, HealthStatus } = require('../helpers/constants');
|
|
const env = require('../helpers/env');
|
|
const { getQueueStats, getDLQConnection, closeConnection } = require('../helpers/dlq_queue_setup');
|
|
|
|
/**
|
|
* Check DLQ (Dead Letter Queue) health for multiple queues
|
|
* @param {Array<string>} queueNames - Array of queue names to check (defaults to partner_tasks)
|
|
*/
|
|
async function checkDLQHealth(queueNames = [env.QUEUE_NAME_PARTNER]) {
|
|
let connection, channel;
|
|
|
|
try {
|
|
// Connect to RabbitMQ using helper
|
|
connection = await getDLQConnection();
|
|
channel = await connection.createChannel();
|
|
|
|
const threshold = env.DLQ_ALERT_THRESHOLD;
|
|
const critical = env.DLQ_ALERT_CRITICAL;
|
|
|
|
// Check all queues
|
|
const queues = {};
|
|
let overallMessageCount = 0;
|
|
let worstStatus = HealthStatus.HEALTHY;
|
|
|
|
for (const queueName of queueNames) {
|
|
const dlqName = `${queueName}_dlq`;
|
|
|
|
try {
|
|
const stats = await getQueueStats(channel, dlqName);
|
|
const messageCount = stats.messageCount;
|
|
overallMessageCount += messageCount;
|
|
|
|
// Determine status for this queue
|
|
let status = HealthStatus.HEALTHY;
|
|
let message = 'Operating normally';
|
|
|
|
if (messageCount >= critical) {
|
|
status = HealthStatus.UNHEALTHY;
|
|
message = `Critical: ${messageCount} messages (threshold: ${critical})`;
|
|
} else if (messageCount >= threshold) {
|
|
status = HealthStatus.DEGRADED;
|
|
message = `Warning: ${messageCount} messages (threshold: ${threshold})`;
|
|
}
|
|
|
|
// Track worst status
|
|
if (status === HealthStatus.UNHEALTHY) {
|
|
worstStatus = HealthStatus.UNHEALTHY;
|
|
} else if (status === HealthStatus.DEGRADED && worstStatus === HealthStatus.HEALTHY) {
|
|
worstStatus = HealthStatus.DEGRADED;
|
|
}
|
|
|
|
queues[queueName] = {
|
|
status,
|
|
message,
|
|
dlqName,
|
|
messageCount,
|
|
consumerCount: stats.consumerCount
|
|
};
|
|
|
|
} catch (error) {
|
|
queues[queueName] = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message,
|
|
message: 'Unable to check DLQ'
|
|
};
|
|
worstStatus = HealthStatus.UNHEALTHY;
|
|
}
|
|
}
|
|
|
|
// Overall summary
|
|
let overallMessage = 'All DLQs operating normally';
|
|
if (worstStatus === HealthStatus.UNHEALTHY) {
|
|
overallMessage = `Critical: ${overallMessageCount} total messages across ${queueNames.length} queue(s)`;
|
|
} else if (worstStatus === HealthStatus.DEGRADED) {
|
|
overallMessage = `Warning: ${overallMessageCount} total messages across ${queueNames.length} queue(s)`;
|
|
}
|
|
|
|
return {
|
|
status: worstStatus,
|
|
message: overallMessage,
|
|
totalMessages: overallMessageCount,
|
|
threshold,
|
|
critical,
|
|
consumerEnabled: env.DLQ_CONSUMER_ENABLED,
|
|
retentionDays: env.DLQ_RETENTION_DAYS,
|
|
queues
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message,
|
|
message: 'Unable to connect to RabbitMQ'
|
|
};
|
|
} finally {
|
|
await closeConnection(connection, channel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Basic health check endpoint
|
|
*/
|
|
async function healthCheck_get(req, res) {
|
|
try {
|
|
const health = {
|
|
timestamp: new Date(),
|
|
overall: HealthStatus.HEALTHY,
|
|
components: {}
|
|
};
|
|
|
|
// Check database connectivity
|
|
try {
|
|
const dbState = mongoose.connection.readyState;
|
|
const dbStatus = dbState === 1 ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;
|
|
|
|
health.components.database = {
|
|
status: dbStatus,
|
|
state: dbState,
|
|
message: dbState === 1 ? 'Connected' : 'Disconnected'
|
|
};
|
|
} catch (error) {
|
|
health.components.database = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message
|
|
};
|
|
health.overall = HealthStatus.UNHEALTHY;
|
|
}
|
|
|
|
// Check partner system users
|
|
try {
|
|
const activeUsers = await PartnerSystemUser.countDocuments({ active: true });
|
|
const errorUsers = await PartnerSystemUser.countDocuments({ syncStatus: SyncStatus.ERROR });
|
|
|
|
const errorRate = activeUsers > 0 ? (errorUsers / activeUsers * 100) : 0;
|
|
const status = errorRate > 50 ? HealthStatus.DEGRADED : HealthStatus.HEALTHY;
|
|
|
|
health.components.partnerUsers = {
|
|
status,
|
|
activeUsers,
|
|
errorUsers,
|
|
errorRate: Math.round(errorRate * 100) / 100
|
|
};
|
|
|
|
if (status === HealthStatus.DEGRADED) {
|
|
health.overall = HealthStatus.DEGRADED;
|
|
}
|
|
} catch (error) {
|
|
health.components.partnerUsers = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message
|
|
};
|
|
health.overall = HealthStatus.UNHEALTHY;
|
|
}
|
|
|
|
// Check partner services
|
|
try {
|
|
const partnerHealth = await partnerSyncService.healthCheck();
|
|
health.components.partnerServices = partnerHealth;
|
|
|
|
if (partnerHealth.overall !== HealthStatus.HEALTHY) {
|
|
health.overall = partnerHealth.overall;
|
|
}
|
|
} catch (error) {
|
|
health.components.partnerServices = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message
|
|
};
|
|
health.overall = HealthStatus.UNHEALTHY;
|
|
}
|
|
|
|
// Check application health
|
|
const memoryUsage = process.memoryUsage();
|
|
const uptime = process.uptime();
|
|
const memoryThreshold = 1024 * 1024 * 1024; // 1GB
|
|
|
|
health.components.application = {
|
|
status: memoryUsage.heapUsed < memoryThreshold ? HealthStatus.HEALTHY : HealthStatus.DEGRADED,
|
|
memoryUsedMB: Math.round(memoryUsage.heapUsed / 1024 / 1024),
|
|
uptimeHours: Math.round(uptime / 3600 * 100) / 100,
|
|
nodeVersion: process.version
|
|
};
|
|
|
|
if (health.components.application.status === HealthStatus.DEGRADED && health.overall === HealthStatus.HEALTHY) {
|
|
health.overall = HealthStatus.DEGRADED;
|
|
}
|
|
|
|
// Check DLQ (Dead Letter Queue) health
|
|
try {
|
|
const dlqHealth = await checkDLQHealth();
|
|
health.components.dlq = dlqHealth;
|
|
|
|
if (dlqHealth.status === HealthStatus.UNHEALTHY) {
|
|
health.overall = HealthStatus.UNHEALTHY;
|
|
} else if (dlqHealth.status === HealthStatus.DEGRADED && health.overall === HealthStatus.HEALTHY) {
|
|
health.overall = HealthStatus.DEGRADED;
|
|
}
|
|
} catch (error) {
|
|
health.components.dlq = {
|
|
status: HealthStatus.UNHEALTHY,
|
|
error: error.message
|
|
};
|
|
// Don't fail overall health if DLQ check fails
|
|
}
|
|
|
|
// Set HTTP status code based on health
|
|
const statusCode = health.overall === HealthStatus.HEALTHY ? 200 :
|
|
health.overall === HealthStatus.DEGRADED ? 200 : 503;
|
|
|
|
res.status(statusCode).json(health);
|
|
|
|
} catch (error) {
|
|
res.status(503).json({
|
|
timestamp: new Date(),
|
|
overall: HealthStatus.UNHEALTHY,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Partner statistics endpoint
|
|
*/
|
|
async function partnerStats_get(req, res) {
|
|
try {
|
|
const stats = {
|
|
timestamp: new Date(),
|
|
partners: {},
|
|
summary: {}
|
|
};
|
|
|
|
// Get partner statistics
|
|
const partnerStats = await PartnerSystemUser.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: 'users',
|
|
localField: 'partner',
|
|
foreignField: '_id',
|
|
as: 'partner'
|
|
}
|
|
},
|
|
{
|
|
$unwind: '$partner'
|
|
},
|
|
{
|
|
$group: {
|
|
_id: '$partner.partnerCode',
|
|
totalUsers: { $sum: 1 },
|
|
activeUsers: {
|
|
$sum: { $cond: [{ $eq: ['$active', true] }, 1, 0] }
|
|
},
|
|
errorUsers: {
|
|
$sum: { $cond: [{ $eq: ['$syncStatus', SyncStatus.ERROR] }, 1, 0] }
|
|
},
|
|
lastSyncUsers: {
|
|
$sum: {
|
|
$cond: [
|
|
{
|
|
$gte: [
|
|
'$lastSyncAt',
|
|
new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
|
|
]
|
|
},
|
|
1,
|
|
0
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]);
|
|
|
|
// Process partner statistics
|
|
for (const partnerStat of partnerStats) {
|
|
stats.partners[partnerStat._id] = {
|
|
totalUsers: partnerStat.totalUsers,
|
|
activeUsers: partnerStat.activeUsers,
|
|
errorUsers: partnerStat.errorUsers,
|
|
lastSyncUsers: partnerStat.lastSyncUsers,
|
|
errorRate: partnerStat.activeUsers > 0 ?
|
|
Math.round((partnerStat.errorUsers / partnerStat.activeUsers) * 100 * 100) / 100 : 0
|
|
};
|
|
}
|
|
|
|
// Calculate summary
|
|
stats.summary = {
|
|
totalPartners: Object.keys(stats.partners).length,
|
|
totalPartnerUsers: partnerStats.reduce((sum, p) => sum + p.totalUsers, 0),
|
|
activePartnerUsers: partnerStats.reduce((sum, p) => sum + p.activeUsers, 0),
|
|
recentErrors: partnerStats.reduce((sum, p) => sum + p.errorUsers, 0),
|
|
availableServices: partnerSyncService.getAvailablePartners()
|
|
};
|
|
|
|
res.json(stats);
|
|
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
error: error.message,
|
|
timestamp: new Date()
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
healthCheck_get,
|
|
partnerStats_get
|
|
};
|