Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions libs/health/src/controller/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Controller, Get, HttpException, HttpStatus, Inject, Logger } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
import { HealthService } from '../health.service';
import { GetHealthSwagger, GetPingSwagger } from './health.swagger';
import { ApiTags } from '@nestjs/swagger';

@SkipThrottle()
@Controller()
@ApiTags('System')
export class HealthController {
private logger = new Logger(HealthController.name);

constructor(
private readonly healthService: HealthService,
@Inject('SERVICE_NAME') private readonly serviceName: string,
) {}

@Get('health')
@GetHealthSwagger()
async checkHealth() {
const pingData = await this.healthService.getHealthData();

if (pingData.status !== 'up') {
this.logger.error(`${this.serviceName} is unhealthy!`);
throw new HttpException(
`${this.serviceName} service is unhealthy.`,
HttpStatus.SERVICE_UNAVAILABLE,
);
}

return 'healthy';
}

@Get('ping')
@GetPingSwagger()
async ping() {
return this.healthService.getHealthData();
}
}
26 changes: 26 additions & 0 deletions libs/health/src/controller/health.swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { applyDecorators } from '@nestjs/common';
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
import { HealthResponse } from '../dtos';

export const GetHealthSwagger = () =>
applyDecorators(
ApiOperation({
summary: 'Краткий статус (Health Check)',
description: 'Используется внешними системами для проверки доступности сервиса.',
}),
ApiResponse({ status: 200, description: 'Сервис работает нормально', type: String }),
ApiResponse({ status: 503, description: 'Сервис недоступен или критическая ошибка' }),
);

export const GetPingSwagger = () =>
applyDecorators(
ApiOperation({
summary: 'Детальный дамп состояния',
description: 'Возвращает аптайм, время старта и метрики памяти.',
}),
ApiResponse({
status: 200,
description: 'Полная статистика сервиса',
type: HealthResponse.Output,
}),
);
25 changes: 25 additions & 0 deletions libs/health/src/dtos/health.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createZodDto } from 'node_modules/nestjs-zod/dist/dto.cjs';
import { z } from 'zod/v4';

const HealthResponseSchema = z.object({
service: z.string().describe('Название сервиса'),
status: z.enum(['up', 'down']).describe('Текущий статус'),
info: z.object({
version: z.string().describe('Версия приложения'),
node: z.string().describe('Версия Node.js'),
pid: z.number().describe('ID процесса'),
}),
time: z.object({
now: z.string().datetime().describe('Текущее время сервера'),
startedAt: z.string().datetime().describe('Время старта сервера'),
uptime: z.string().describe('Аптайм в формате ч/м/с'),
uptimeSeconds: z.number().describe('Аптайм в секундах'),
}),
metrics: z.object({
rss: z.string().describe('Resident Set Size (общая память)'),
heapUsed: z.string().describe('Использованная память в куче'),
loadAverage: z.string().describe('Средняя нагрузка на CPU'),
}),
});

export class HealthResponse extends createZodDto(HealthResponseSchema) {}
1 change: 1 addition & 0 deletions libs/health/src/dtos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HealthResponse } from './health.dto';
21 changes: 21 additions & 0 deletions libs/health/src/health.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type DynamicModule, Global, Module } from '@nestjs/common';
import { HealthController } from './controller/health.controller';
import { HealthService } from './health.service';

@Global()
@Module({})
export class HealthModule {
static register(serviceName: string): DynamicModule {
return {
module: HealthModule,
providers: [
{
provide: 'SERVICE_NAME',
useValue: serviceName,
},
HealthService,
],
controllers: [HealthController],
};
}
}
51 changes: 51 additions & 0 deletions libs/health/src/health.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Inject, Injectable } from '@nestjs/common';
import * as os from 'os';

@Injectable()
export class HealthService {
private readonly startTime: Date;

constructor(
@Inject('SERVICE_NAME')
private readonly serviceName: string,
) {
this.startTime = new Date();
}

async getHealthData() {
const uptimeSeconds = Math.floor(process.uptime());
const mem = process.memoryUsage();

return {
service: this.serviceName,
status: 'up',
info: {
version: '1.0.0',
node: process.version,
pid: process.pid,
},
time: {
now: new Date().toISOString(),
startedAt: this.startTime.toISOString(),
uptime: this.formatUptime(uptimeSeconds),
uptimeSeconds: uptimeSeconds,
},
metrics: {
rss: this.toMb(mem.rss),
heapUsed: this.toMb(mem.heapUsed),
loadAverage: os.loadavg()[0].toFixed(2),
},
};
}

private toMb(bytes: number) {
return `${Math.round(bytes / 1024 / 1024)}MB`;
}

private formatUptime(seconds: number) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h}h ${m}m ${s}s`;
}
}
1 change: 1 addition & 0 deletions libs/health/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './health.module';
9 changes: 9 additions & 0 deletions libs/health/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "../../dist/libs/health"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}
17 changes: 13 additions & 4 deletions nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
"webpack": true
},
"projects": {
"bootstrap": {
"type": "library",
"root": "libs/bootstrap",
"entryFile": "index",
"sourceRoot": "libs/bootstrap/src",
"compilerOptions": {
"tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
}
},
"config": {
"type": "library",
"root": "libs/config",
Expand All @@ -25,13 +34,13 @@
"tsConfigPath": "libs/database/tsconfig.lib.json"
}
},
"bootstrap": {
"health": {
"type": "library",
"root": "libs/bootstrap",
"root": "libs/health",
"entryFile": "index",
"sourceRoot": "libs/bootstrap/src",
"sourceRoot": "libs/health/src",
"compilerOptions": {
"tsConfigPath": "libs/bootstrap/tsconfig.lib.json"
"tsConfigPath": "libs/health/tsconfig.lib.json"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@nestjs/platform-fastify": "^11.1.18",
"@nestjs/swagger": "^11.2.7",
"@nestjs/throttler": "^6.5.0",
"@willsoto/nestjs-prometheus": "^6.1.0",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
"fastify": "^5.8.4",
Expand Down
Loading
Loading