Major Updates to Network and Load Balancing
This commit is contained in:
259
server/api/health.ts
Normal file
259
server/api/health.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import { getNocoDbConfiguration } from '../utils/nocodb';
|
||||
import { getMinioClient } from '../utils/minio';
|
||||
import { useDirectus } from '#imports';
|
||||
|
||||
interface HealthCheckResult {
|
||||
status: 'healthy' | 'unhealthy' | 'degraded';
|
||||
timestamp: string;
|
||||
uptime: number;
|
||||
services: {
|
||||
[key: string]: {
|
||||
status: 'up' | 'down' | 'slow';
|
||||
responseTime?: number;
|
||||
error?: string;
|
||||
};
|
||||
};
|
||||
memory: {
|
||||
used: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
};
|
||||
environment: {
|
||||
nodeVersion: string;
|
||||
configStatus: 'complete' | 'partial' | 'missing';
|
||||
missingVars?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event): Promise<HealthCheckResult> => {
|
||||
const startTime = Date.now();
|
||||
const result: HealthCheckResult = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
services: {},
|
||||
memory: {
|
||||
used: 0,
|
||||
total: 0,
|
||||
percentage: 0
|
||||
},
|
||||
environment: {
|
||||
nodeVersion: process.version,
|
||||
configStatus: 'complete'
|
||||
}
|
||||
};
|
||||
|
||||
// Memory check
|
||||
const memUsage = process.memoryUsage();
|
||||
result.memory = {
|
||||
used: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
|
||||
total: Math.round(memUsage.heapTotal / 1024 / 1024), // MB
|
||||
percentage: Math.round((memUsage.heapUsed / memUsage.heapTotal) * 100)
|
||||
};
|
||||
|
||||
// Check if memory usage is too high
|
||||
if (result.memory.percentage > 90) {
|
||||
result.status = 'unhealthy';
|
||||
} else if (result.memory.percentage > 80) {
|
||||
result.status = 'degraded';
|
||||
}
|
||||
|
||||
// Environment variables check
|
||||
const requiredEnvVars = [
|
||||
'NUXT_NOCODB_URL',
|
||||
'NUXT_NOCODB_TOKEN',
|
||||
'NUXT_MINIO_ACCESS_KEY',
|
||||
'NUXT_MINIO_SECRET_KEY',
|
||||
'NUXT_DOCUMENSO_API_KEY',
|
||||
'NUXT_DOCUMENSO_BASE_URL'
|
||||
];
|
||||
|
||||
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
|
||||
if (missingVars.length > 0) {
|
||||
result.environment.configStatus = missingVars.length > 3 ? 'missing' : 'partial';
|
||||
result.environment.missingVars = missingVars;
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
|
||||
// Service health checks
|
||||
|
||||
// 1. NocoDB Check
|
||||
try {
|
||||
const nocoStart = Date.now();
|
||||
const nocoConfig = getNocoDbConfiguration();
|
||||
|
||||
if (!nocoConfig.url || !nocoConfig.token) {
|
||||
result.services.nocodb = {
|
||||
status: 'down',
|
||||
error: 'Missing configuration'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
} else {
|
||||
const response = await fetch(`${nocoConfig.url}/api/v1/health`, {
|
||||
headers: {
|
||||
'xc-token': nocoConfig.token
|
||||
},
|
||||
signal: AbortSignal.timeout(5000) // 5 second timeout
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - nocoStart;
|
||||
|
||||
if (response.ok) {
|
||||
result.services.nocodb = {
|
||||
status: responseTime > 3000 ? 'slow' : 'up',
|
||||
responseTime
|
||||
};
|
||||
if (responseTime > 3000) result.status = 'degraded';
|
||||
} else {
|
||||
result.services.nocodb = {
|
||||
status: 'down',
|
||||
responseTime,
|
||||
error: `HTTP ${response.status}`
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
result.services.nocodb = {
|
||||
status: 'down',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
|
||||
// 2. Directus Check
|
||||
try {
|
||||
const directusStart = Date.now();
|
||||
const { $directus } = useNuxtApp();
|
||||
|
||||
// Check if Directus URL is configured
|
||||
const directusUrl = useRuntimeConfig().public.directus?.url;
|
||||
if (!directusUrl) {
|
||||
result.services.directus = {
|
||||
status: 'down',
|
||||
error: 'Missing configuration'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
} else {
|
||||
const response = await fetch(`${directusUrl}/server/health`, {
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - directusStart;
|
||||
|
||||
if (response.ok) {
|
||||
result.services.directus = {
|
||||
status: responseTime > 3000 ? 'slow' : 'up',
|
||||
responseTime
|
||||
};
|
||||
if (responseTime > 3000) result.status = 'degraded';
|
||||
} else {
|
||||
result.services.directus = {
|
||||
status: 'down',
|
||||
responseTime,
|
||||
error: `HTTP ${response.status}`
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
result.services.directus = {
|
||||
status: 'down',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
|
||||
// 3. MinIO Check
|
||||
try {
|
||||
const minioStart = Date.now();
|
||||
const minioClient = getMinioClient();
|
||||
const bucketName = useRuntimeConfig().minio.bucketName;
|
||||
|
||||
// Try to check if bucket exists
|
||||
const bucketExists = await minioClient.bucketExists(bucketName);
|
||||
const responseTime = Date.now() - minioStart;
|
||||
|
||||
if (bucketExists) {
|
||||
result.services.minio = {
|
||||
status: responseTime > 3000 ? 'slow' : 'up',
|
||||
responseTime
|
||||
};
|
||||
if (responseTime > 3000) result.status = 'degraded';
|
||||
} else {
|
||||
result.services.minio = {
|
||||
status: 'down',
|
||||
responseTime,
|
||||
error: 'Bucket not found'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
} catch (error) {
|
||||
result.services.minio = {
|
||||
status: 'down',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
|
||||
// 4. Documenso Check
|
||||
try {
|
||||
const documensoStart = Date.now();
|
||||
const documensoUrl = process.env.NUXT_DOCUMENSO_BASE_URL;
|
||||
const documensoKey = process.env.NUXT_DOCUMENSO_API_KEY;
|
||||
|
||||
if (!documensoUrl || !documensoKey) {
|
||||
result.services.documenso = {
|
||||
status: 'down',
|
||||
error: 'Missing configuration'
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
} else {
|
||||
const response = await fetch(`${documensoUrl}/api/health`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${documensoKey}`
|
||||
},
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - documensoStart;
|
||||
|
||||
if (response.ok || response.status === 401) { // 401 means API is up but key might be wrong
|
||||
result.services.documenso = {
|
||||
status: responseTime > 3000 ? 'slow' : 'up',
|
||||
responseTime
|
||||
};
|
||||
if (responseTime > 3000) result.status = 'degraded';
|
||||
} else {
|
||||
result.services.documenso = {
|
||||
status: 'down',
|
||||
responseTime,
|
||||
error: `HTTP ${response.status}`
|
||||
};
|
||||
result.status = 'unhealthy';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
result.services.documenso = {
|
||||
status: 'down',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
// Documenso being down shouldn't make the whole app unhealthy
|
||||
if (result.status === 'healthy') result.status = 'degraded';
|
||||
}
|
||||
|
||||
// Overall response time
|
||||
const totalTime = Date.now() - startTime;
|
||||
if (totalTime > 10000 && result.status === 'healthy') {
|
||||
result.status = 'degraded';
|
||||
}
|
||||
|
||||
// Set appropriate HTTP status code
|
||||
if (result.status === 'unhealthy') {
|
||||
setResponseStatus(event, 503);
|
||||
} else if (result.status === 'degraded') {
|
||||
setResponseStatus(event, 200); // Still return 200 for degraded to not trigger Nginx failures
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
Reference in New Issue
Block a user