@texagon/nestjs-health
Version:
A comprehensive health monitoring package for NestJS applications with metrics and Kubernetes-ready health checks
259 lines (241 loc) • 8.29 kB
text/typescript
import {
Controller,
Get,
VERSION_NEUTRAL,
Version,
Query,
Inject,
Optional,
UseGuards,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiQuery,
ApiResponse,
ApiSecurity,
} from "@nestjs/swagger";
import {
HealthCheck,
HealthCheckService,
MemoryHealthIndicator,
DiskHealthIndicator,
} from "@nestjs/terminus";
import { HealthService } from "./health.service";
import { MetricsService } from "./metrics.service";
import { HealthModuleOptions, MODULE_OPTIONS_TOKEN } from "./health.module";
import { HealthAuthGuard } from "./health-auth.guard";
export class HealthController {
private readonly options: any;
constructor(
private health: HealthCheckService,
private memory: MemoryHealthIndicator,
private disk: DiskHealthIndicator,
private healthService: HealthService,
private metricsService: MetricsService,
options: HealthModuleOptions,
) {
this.options = options || {
enableMemoryMonitoring: true,
enableDiskMonitoring: true,
diskPath: "/",
diskThresholdPercent: 0.75,
routePrefix: "health",
};
}
check() {
const checks = [() => this.healthService.isHealthy()];
// Add memory checks if enabled
if (this.options.enableMemoryMonitoring) {
checks.push(() =>
this.memory.checkHeap("memory_heap", 150 * 1024 * 1024),
);
checks.push(() => this.memory.checkRSS("memory_rss", 150 * 1024 * 1024));
}
// Add disk checks if enabled
if (this.options.enableDiskMonitoring) {
checks.push(() =>
this.disk.checkStorage("disk", {
path: this.options.diskPath,
thresholdPercent: this.options.diskThresholdPercent,
}),
);
}
return this.health.check(checks);
}
liveness() {
return this.health.check([() => this.healthService.isHealthy()]);
}
readiness() {
return this.health.check([
() => this.healthService.isHealthy(),
() => this.memory.checkHeap("memory_heap", 300 * 1024 * 1024),
]);
}
async getDetailedStats( includeHistory: string) {
const startTime = process.uptime();
const memoryStats = await this.healthService.getMemoryStats();
// Get health status safely with try/catch to handle possible HealthCheckError
let healthStatus = "unhealthy";
let healthDetails = {};
try {
const health = await this.healthService.isHealthy();
healthStatus = "healthy";
healthDetails = health;
} catch (error) {
healthStatus = "unhealthy";
if (error.response) {
healthDetails = error.response;
}
}
const diskStats = this.options.enableDiskMonitoring
? await this.disk.checkStorage("disk", {
path: this.options.diskPath,
thresholdPercent: this.options.diskThresholdPercent,
})
: { disk: { status: "unknown" } };
const os = require("os");
const cpuUsage = process.cpuUsage();
const systemUptime = os.uptime();
const loadAvg = os.loadavg();
const metrics = this.metricsService.getMetrics();
return {
status: healthStatus,
timestamp: new Date().toISOString(),
version: process.env.npm_package_version || "1.0.0",
process: {
pid: process.pid,
ppid: process.ppid,
title: process.title,
uptime: {
seconds: startTime,
formatted: this.formatUptime(startTime),
},
memoryUsage: {
current: {
rss: this.formatBytes(process.memoryUsage().rss),
heapTotal: this.formatBytes(process.memoryUsage().heapTotal),
heapUsed: this.formatBytes(process.memoryUsage().heapUsed),
external: this.formatBytes(process.memoryUsage().external || 0),
arrayBuffers: this.formatBytes(
process.memoryUsage().arrayBuffers || 0,
),
},
max: {
rss: this.formatBytes(metrics.max.memory.rss),
heapTotal: this.formatBytes(metrics.max.memory.heapTotal),
heapUsed: this.formatBytes(metrics.max.memory.heapUsed),
external: this.formatBytes(metrics.max.memory.external),
arrayBuffers: this.formatBytes(
metrics.max.memory.arrayBuffers || 0,
),
},
min: {
rss: this.formatBytes(metrics.min.memory.rss),
heapTotal: this.formatBytes(metrics.min.memory.heapTotal),
heapUsed: this.formatBytes(metrics.min.memory.heapUsed),
external: this.formatBytes(metrics.min.memory.external),
arrayBuffers: this.formatBytes(
metrics.min.memory.arrayBuffers || 0,
),
},
},
cpuUsage: {
current: {
user: cpuUsage.user,
system: cpuUsage.system,
percent: metrics.current?.cpu.percent.toFixed(2) + "%" || "0%",
},
max: {
user: metrics.max.cpu.user,
system: metrics.max.cpu.system,
percent: metrics.max.cpu.percent.toFixed(2) + "%",
},
},
},
system: {
platform: process.platform,
arch: process.arch,
nodeVersion: process.version,
hostname: os.hostname(),
uptime: {
seconds: systemUptime,
formatted: this.formatUptime(systemUptime),
},
loadAvg: os.loadavg(),
totalMemory: this.formatBytes(os.totalmem()),
freeMemory: this.formatBytes(os.freemem()),
cpus: os.cpus().length,
},
disk: diskStats,
environment: process.env.NODE_ENV || "development",
requests: {
total: metrics.requests.stats.total,
responseTime: {
avg: metrics.requests.stats.avgResponseTime.toFixed(2) + "ms",
max: metrics.requests.stats.maxResponseTime + "ms",
min:
metrics.requests.stats.minResponseTime === Number.MAX_SAFE_INTEGER
? "0ms"
: metrics.requests.stats.minResponseTime + "ms",
},
byMethod: metrics.requests.stats.byMethod,
byStatusCode: metrics.requests.stats.byStatusCode,
recent: metrics.requests.recent,
},
...(includeHistory === "true" && { history: metrics.history }),
};
}
private formatBytes(bytes: number): string {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
private formatUptime(seconds: number): string {
const days = Math.floor(seconds / (3600 * 24));
const hours = Math.floor((seconds % (3600 * 24)) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${days}d ${hours}h ${minutes}m ${secs}s`;
}
}