Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Health Checks - diagnostyka stanu fortu

Główny inżynierze! Konsul Caesar.js potrzebuje Twojej ekspertyzy w monitorowaniu stanu technicznego naszego kastrum. Health Checks to jak systematyczne przeglądy wszystkich umocnień - od bram po stan wałów obronnych. Tylko dzięki regularnej diagnostyce możemy zapewnić, że nasz fort będzie gotowy na każdą wyprawę!

W rzymskim kastrum awaria kluczowego systemu może oznaczać różnicę między życiem a śmiercią. Dlatego doświadczeni inżynierowie regularnie sprawdzają stan machin oblężniczych, integralność murów, zapasy w spichlerzach i działanie systemów sygnalizacyjnych. Health Checks w NestJS działają podobnie - monitorują kluczowe komponenty aplikacji.

Podstawowy System Health Checks

Konfiguracja Health Check Module

1// health/health.module.ts
2import { Module } from '@nestjs/common';
3import { TerminusModule } from '@nestjs/terminus';
4import { HttpModule } from '@nestjs/axios';
5import { TypeOrmModule } from '@nestjs/typeorm';
6import { LegionaryHealthController } from './legionariusze-health.controller';
7import { LegionaryHealthService } from './legionariusze-health.service';
8import { TributeHealthIndicator } from './indicators/tribute-health.indicator';
9import { LegionHealthIndicator } from './indicators/legion-health.indicator';
10
11@Module({
12 imports: [
13 TerminusModule,
14 HttpModule,
15 TypeOrmModule.forFeature([/* entities */])
16 ],
17 controllers: [LegionaryHealthController],
18 providers: [
19 LegionaryHealthService,
20 TributeHealthIndicator,
21 LegionHealthIndicator
22 ]
23})
24export class HealthModule {}

Główny Health Controller

1// health/legionariusze-health.controller.ts
2import { Controller, Get, Param } from '@nestjs/common';
3import {
4 HealthCheckService,
5 HealthCheck,
6 TypeOrmHealthIndicator,
7 HttpHealthIndicator,
8 MemoryHealthIndicator,
9 DiskHealthIndicator
10} from '@nestjs/terminus';
11import { LegionaryHealthService } from './legionariusze-health.service';
12import { TributeHealthIndicator } from './indicators/tribute-health.indicator';
13import { LegionHealthIndicator } from './indicators/legion-health.indicator';
14
15@Controller('health')
16export class LegionaryHealthController {
17 constructor(
18 private health: HealthCheckService,
19 private db: TypeOrmHealthIndicator,
20 private http: HttpHealthIndicator,
21 private memory: MemoryHealthIndicator,
22 private disk: DiskHealthIndicator,
23 private legionariuszeHealth: LegionaryHealthService,
24 private tributeHealth: TributeHealthIndicator,
25 private legionHealth: LegionHealthIndicator
26 ) {}
27
28 @Get()
29 @HealthCheck()
30 checkLegionHealth() {
31 return this.health.check([
32 // Podstawowe systemy fortu
33 () => this.db.pingCheck('database', { timeout: 1500 }),
34 () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
35 () => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
36 () => this.disk.checkStorage('storage', { thresholdPercent: 0.8, path: '/' }),
37 
38 // Systemy specjalistyczne
39 () => this.tributeHealth.checkTributeSystem('tribute_system'),
40 () => this.legionHealth.checkLegionSystem('legion_system'),
41 () => this.legionariuszeHealth.checkLegionIntegrity('cohort_integrity'),
42 () => this.legionariuszeHealth.checkWeatherSystem('weather_system')
43 ]);
44 }
45
46 @Get('quick')
47 @HealthCheck()
48 quickHealthCheck() {
49 return this.health.check([
50 () => this.db.pingCheck('database', { timeout: 500 }),
51 () => this.legionariuszeHealth.checkBasicSystems('basic_systems')
52 ]);
53 }
54
55 @Get('detailed')
56 @HealthCheck()
57 detailedHealthCheck() {
58 return this.health.check([
59 // Podstawowe systemy
60 () => this.db.pingCheck('database', { timeout: 3000 }),
61 () => this.memory.checkHeap('memory_heap', 200 * 1024 * 1024),
62 () => this.memory.checkRSS('memory_rss', 200 * 1024 * 1024),
63 () => this.disk.checkStorage('disk_usage', { thresholdPercent: 0.9, path: '/' }),
64 
65 // Systemy zewnętrzne
66 () => this.http.pingCheck('weather_api', 'https://api.weather.legionariusze/status'),
67 () => this.http.pingCheck('tribute_market', 'https://market.tributes.com/health'),
68 
69 // Systemy funkcjonalne
70 () => this.tributeHealth.checkTributeSystem('tribute_system'),
71 () => this.tributeHealth.checkTributeIntegrity('tribute_integrity'),
72 () => this.legionHealth.checkLegionSystem('legion_system'),
73 () => this.legionHealth.checkLegionMorale('legion_morale'),
74 
75 // Systemy operacyjne
76 () => this.legionariuszeHealth.checkLegionIntegrity('cohort_integrity'),
77 () => this.legionariuszeHealth.checkWeatherSystem('weather_system'),
78 () => this.legionariuszeHealth.checkNavigationSystem('navigation_system'),
79 () => this.legionariuszeHealth.checkCommunicationSystem('communication_system')
80 ]);
81 }
82
83 @Get('systems/:system')
84 @HealthCheck()
85 checkSpecificSystem(@Param('system') system: string) {
86 const systemChecks = {
87 'tribute': () => this.tributeHealth.checkTributeSystem('tribute_system'),
88 'legion': () => this.legionHealth.checkLegionSystem('legion_system'),
89 'database': () => this.db.pingCheck('database'),
90 'memory': () => this.memory.checkHeap('memory', 100 * 1024 * 1024),
91 'navigation': () => this.legionariuszeHealth.checkNavigationSystem('navigation'),
92 'weather': () => this.legionariuszeHealth.checkWeatherSystem('weather')
93 };
94
95 const check = systemChecks[system];
96 if (!check) {
97 throw new NotFoundException(`System '${system}' not found. Available: ${Object.keys(systemChecks).join(', ')}`);
98 }
99
100 return this.health.check([check]);
101 }
102}

Custom Health Indicators

Tribute System Health Indicator

1// indicators/tribute-health.indicator.ts
2import { Injectable } from '@nestjs/common';
3import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';
4import { InjectRepository } from '@nestjs/typeorm';
5import { Repository } from 'typeorm';
6import { Tribute } from '../entities/tribute.entity';
7
8@Injectable()
9export class TributeHealthIndicator extends HealthIndicator {
10 constructor(
11 @InjectRepository(Tribute)
12 private tributeRepository: Repository<Tribute>
13 ) {
14 super();
15 }
16
17 async checkTributeSystem(key: string): Promise<HealthIndicatorResult> {
18 try {
19 const startTime = Date.now();
20 
21 // Sprawdź podstawowe operacje na tributach
22 const tributeCount = await this.tributeRepository.count();
23 const recentTributes = await this.tributeRepository.find({
24 take: 10,
25 order: { collectedAt: 'DESC' }
26 });
27 
28 const responseTime = Date.now() - startTime;
29 
30 // Sprawdź integralność danych
31 const cursedTributes = await this.tributeRepository.count({ where: { isCursed: true } });
32 const cursedPercentage = tributeCount > 0 ? (cursedTributes / tributeCount) * 100 : 0;
33 
34 // Sprawdź czy system odpowiada w rozsądnym czasie
35 if (responseTime > 5000) {
36 throw new HealthCheckError(
37 'Tribute system responding slowly',
38 this.getStatus(key, false, {
39 responseTime,
40 message: 'System tributów odpowiada zbyt wolno!'
41 })
42 );
43 }
44 
45 // Sprawdź czy nie mamy zbyt wielu przeklętych tributów
46 if (cursedPercentage > 50) {
47 throw new HealthCheckError(
48 'Too many cursed tributes detected',
49 this.getStatus(key, false, {
50 cursedPercentage,
51 message: 'Zbyt wiele przeklętych tributów - możliwe zagrożenie!'
52 })
53 );
54 }
55 
56 return this.getStatus(key, true, {
57 tributeCount,
58 cursedPercentage: Math.round(cursedPercentage),
59 responseTime,
60 lastTribute: recentTributes[0]?.name || 'None',
61 status: 'System tributów działa prawidłowo',
62 recommendation: responseTime > 1000 ? 'Rozważ optymalizację zapytań' : 'System w pełnej sprawności'
63 });
64 
65 } catch (error) {
66 throw new HealthCheckError(
67 'Tribute system check failed',
68 this.getStatus(key, false, {
69 error: error.message,
70 message: 'Błąd w systemie tributów!',
71 action: 'Sprawdź połączenie z bazą danych i integralność tabel'
72 })
73 );
74 }
75 }
76
77 async checkTributeIntegrity(key: string): Promise<HealthIndicatorResult> {
78 try {
79 // Sprawdź integralność relacji
80 const tributesWithoutChest = await this.tributeRepository
81 .createQueryBuilder('tribute')
82 .leftJoin('tribute.chest', 'chest')
83 .where('chest.id IS NULL')
84 .getCount();
85 
86 // Sprawdź duplikaty
87 const duplicateSerialNumbers = await this.tributeRepository
88 .createQueryBuilder('tribute')
89 .select('tribute.serialNumber, COUNT(*) as count')
90 .groupBy('tribute.serialNumber')
91 .having('COUNT(*) > 1')
92 .getRawMany();
93 
94 // Sprawdź wartości
95 const tributesWithInvalidValue = await this.tributeRepository
96 .createQueryBuilder('tribute')
97 .where('tribute.value <= 0 OR tribute.value IS NULL')
98 .getCount();
99 
100 const issues = [];
101 if (tributesWithoutChest > 0) {
102 issues.push(`${tributesWithoutChest} tributów bez przypisanej skrzyni`);
103 }
104 if (duplicateSerialNumbers.length > 0) {
105 issues.push(`${duplicateSerialNumbers.length} duplikatów numerów seryjnych`);
106 }
107 if (tributesWithInvalidValue > 0) {
108 issues.push(`${tributesWithInvalidValue} tributów z nieprawidłową wartością`);
109 }
110 
111 const isHealthy = issues.length === 0;
112 
113 return this.getStatus(key, isHealthy, {
114 issues: issues.length > 0 ? issues : ['Brak problemów z integralnością'],
115 orphanedTributes: tributesWithoutChest,
116 duplicateSerials: duplicateSerialNumbers.length,
117 invalidValues: tributesWithInvalidValue,
118 status: isHealthy ? 'Integralność danych OK' : 'Wykryto problemy z danymi',
119 recommendation: isHealthy ? 'Kontynuuj monitoring' : 'Wymaga natychmiastowej naprawy'
120 });
121 
122 } catch (error) {
123 throw new HealthCheckError(
124 'Tribute integrity check failed',
125 this.getStatus(key, false, { error: error.message })
126 );
127 }
128 }
129}

Legion System Health Indicator

1// indicators/legion-health.indicator.ts
2@Injectable()
3export class LegionHealthIndicator extends HealthIndicator {
4 constructor(
5 @InjectRepository(Legionary) private legionariuszeRepository: Repository<Legionary>
6 ) {
7 super();
8 }
9
10 async checkLegionSystem(key: string): Promise<HealthIndicatorResult> {
11 try {
12 const totalLegion = await this.legionariuszeRepository.count();
13 const activeLegion = await this.legionariuszeRepository.count({ where: { status: 'ACTIVE' } });
14 const centurions = await this.legionariuszeRepository.count({ where: { rank: 'CENTURION' } });
15 const officers = await this.legionariuszeRepository.count({ 
16 where: { rank: In(['OPTIO', 'QUAESTOR']) } 
17 });
18 
19 // Sprawdź czy mamy wystarczający legion
20 if (totalLegion < 10) {
21 throw new HealthCheckError(
22 'Insufficient legion size',
23 this.getStatus(key, false, {
24 totalLegion,
25 message: 'Za mała legion do bezpiecznego żeglowania!',
26 recommendation: 'Zrekrutuj więcej legionariuszów'
27 })
28 );
29 }
30 
31 // Sprawdź strukturę dowodzenia
32 if (centurions === 0) {
33 throw new HealthCheckError(
34 'No centurion available',
35 this.getStatus(key, false, {
36 message: 'Brak centuriona w aplikacji!',
37 recommendation: 'Awansuj doświadczonego legionariusza na centuriona'
38 })
39 );
40 }
41 
42 const activePercentage = (activeLegion / totalLegion) * 100;
43 const officerRatio = (officers / totalLegion) * 100;
44 
45 return this.getStatus(key, true, {
46 totalLegion,
47 activeLegion,
48 centurions,
49 officers,
50 activePercentage: Math.round(activePercentage),
51 officerRatio: Math.round(officerRatio),
52 status: 'legion w dobrej kondycji',
53 morale: activePercentage > 80 ? 'HIGH' : activePercentage > 60 ? 'MEDIUM' : 'LOW'
54 });
55 
56 } catch (error) {
57 throw new HealthCheckError(
58 'Legion system check failed',
59 this.getStatus(key, false, { error: error.message })
60 );
61 }
62 }
63
64 async checkLegionMorale(key: string): Promise<HealthIndicatorResult> {
65 try {
66 // Sprawdź aktywność legionu (ostatnie 24h)
67 const yesterday = new Date();
68 yesterday.setDate(yesterday.getDate() - 1);
69 
70 const recentActivity = await this.legionariuszeRepository
71 .createQueryBuilder('legionariusze')
72 .where('legionariusze.lastActivity >= :yesterday', { yesterday })
73 .getCount();
74 
75 const totalLegion = await this.legionariuszeRepository.count();
76 const activityRate = (recentActivity / totalLegion) * 100;
77 
78 // Sprawdź czy są problemy dyscyplinarne
79 const desertions = await this.legionariuszeRepository.count({ 
80 where: { status: 'DESERTED', updatedAt: MoreThan(yesterday) } 
81 });
82 
83 const mutinyRisk = desertions > 2 ? 'HIGH' : desertions > 0 ? 'MEDIUM' : 'LOW';
84 const overallMorale = activityRate > 70 && mutinyRisk === 'LOW' ? 'HIGH' : 
85 activityRate > 50 && mutinyRisk !== 'HIGH' ? 'MEDIUM' : 'LOW';
86 
87 const isHealthy = overallMorale !== 'LOW';
88 
89 return this.getStatus(key, isHealthy, {
90 activityRate: Math.round(activityRate),
91 recentDesertions: desertions,
92 mutinyRisk,
93 overallMorale,
94 status: isHealthy ? 'Morale legionu w normie' : 'Problemy z morale legionu',
95 recommendation: isHealthy ? 'Kontynuuj obecne zarządzanie' : 'Rozważ poprawę warunków pracy'
96 });
97 
98 } catch (error) {
99 throw new HealthCheckError(
100 'Legion morale check failed',
101 this.getStatus(key, false, { error: error.message })
102 );
103 }
104 }
105}

Main Legionary Health Service

1// health/legionariusze-health.service.ts
2@Injectable()
3export class LegionaryHealthService extends HealthIndicator {
4 constructor(
5 private httpService: HttpService,
6 private configService: ConfigService
7 ) {
8 super();
9 }
10
11 async checkLegionIntegrity(key: string): Promise<HealthIndicatorResult> {
12 try {
13 // Symulacja sprawdzenia integralności fortu
14 const systems = {
15 rampart: await this.checkHullIntegrity(),
16 marches: await this.checkSailsCondition(),
17 ballistae: await this.checkCannonsStatus(),
18 storage: await this.checkStorageCapacity()
19 };
20 
21 const failedSystems = Object.entries(systems)
22 .filter(([_, status]) => !status.healthy)
23 .map(([name]) => name);
24 
25 const isHealthy = failedSystems.length === 0;
26 
27 return this.getStatus(key, isHealthy, {
28 systems,
29 failedSystems,
30 overallCondition: isHealthy ? 'EXCELLENT' : failedSystems.length < 2 ? 'GOOD' : 'POOR',
31 combatReady: isHealthy || failedSystems.length < 2,
32 recommendation: isHealthy ? 'Kohorta gotowa do wymarszu' : 'Wymaga napraw przed wymarszem'
33 });
34 
35 } catch (error) {
36 throw new HealthCheckError(
37 'Legion integrity check failed',
38 this.getStatus(key, false, { error: error.message })
39 );
40 }
41 }
42
43 async checkWeatherSystem(key: string): Promise<HealthIndicatorResult> {
44 try {
45 const weatherApiUrl = this.configService.get('WEATHER_API_URL');
46 
47 if (!weatherApiUrl) {
48 return this.getStatus(key, false, {
49 message: 'Weather API URL not configured',
50 recommendation: 'Configure WEATHER_API_URL environment variable'
51 });
52 }
53 
54 const startTime = Date.now();
55 const response = await this.httpService.get(weatherApiUrl + '/health').toPromise();
56 const responseTime = Date.now() - startTime;
57 
58 const isHealthy = response.status === 200 && responseTime < 3000;
59 
60 return this.getStatus(key, isHealthy, {
61 responseTime,
62 statusCode: response.status,
63 apiVersion: response.data?.version || 'unknown',
64 lastUpdate: response.data?.lastUpdate || 'unknown',
65 recommendation: isHealthy ? 'Weather system operational' : 'Check weather service'
66 });
67 
68 } catch (error) {
69 throw new HealthCheckError(
70 'Weather system check failed',
71 this.getStatus(key, false, {
72 error: error.message,
73 recommendation: 'Check weather service connectivity'
74 })
75 );
76 }
77 }
78
79 async checkBasicSystems(key: string): Promise<HealthIndicatorResult> {
80 try {
81 // Szybkie sprawdzenie podstawowych systemów
82 const basicChecks = await Promise.all([
83 this.quickSystemCheck('engine'),
84 this.quickSystemCheck('power'),
85 this.quickSystemCheck('water'),
86 this.quickSystemCheck('food')
87 ]);
88 
89 const allHealthy = basicChecks.every(check => check.healthy);
90 const criticalIssues = basicChecks.filter(check => !check.healthy && check.critical);
91 
92 return this.getStatus(key, allHealthy && criticalIssues.length === 0, {
93 systems: basicChecks.reduce((acc, check) => {
94 acc[check.name] = { healthy: check.healthy, status: check.status };
95 return acc;
96 }, {}),
97 criticalIssues: criticalIssues.length,
98 overallStatus: allHealthy ? 'ALL_SYSTEMS_GO' : criticalIssues.length > 0 ? 'CRITICAL' : 'MINOR_ISSUES'
99 });
100 
101 } catch (error) {
102 throw new HealthCheckError(
103 'Basic systems check failed',
104 this.getStatus(key, false, { error: error.message })
105 );
106 }
107 }
108
109 // Helper methods
110 private async checkHullIntegrity() {
111 return { healthy: true, condition: 'EXCELLENT', lastInspection: new Date() };
112 }
113
114 private async checkSailsCondition() {
115 return { healthy: true, condition: 'GOOD', efficiency: 95 };
116 }
117
118 private async checkCannonsStatus() {
119 return { healthy: true, operational: 32, total: 32 };
120 }
121
122 private async checkStorageCapacity() {
123 return { healthy: true, used: 65, capacity: 100, unit: 'percent' };
124 }
125
126 private async quickSystemCheck(systemName: string) {
127 const systems = {
128 engine: { healthy: true, status: 'Running normally', critical: true },
129 power: { healthy: true, status: 'Full power available', critical: true },
130 water: { healthy: true, status: 'Fresh water reserves OK', critical: false },
131 food: { healthy: true, status: 'Provisions sufficient', critical: false }
132 };
133 
134 return { name: systemName, ...systems[systemName] };
135 }
136}

Health Checks to Twój system wczesnego ostrzegania - pozwalają wykryć problemy zanim staną się krytyczne. Dobry system diagnostyczny to różnica między bezpieczną wyprawą a klęską na rubieżach Imperium!

Pamiętaj: mądry centurion sprawdza stan fortu przed każdą wyprawą, ale najlepszy centurion monitoruje go nieprzerwanie!

Ir a CodeWorlds