Cours NestJS · Module 6: Gestion des erreurs et monitoring
PROJET : Système complet de monitoring de la flotte
Dans cette leçon4
Amiral en chef de la technologie ! Le consul Caesar.js t'a confié le défi ultime - créer un système complet de monitoring pour toute la flotte romaine. Ce sera un véritable test de toutes les compétences acquises dans le module Error Handling & Monitoring !
Spécification du projet
Ton système doit être une solution complète pour surveiller une flotte composée de nombreux navires romains. Chaque fort est une instance distincte d'une application NestJS, et c'est toi qui construis le système central de monitoring.
Exigences fonctionnelles
1. Monitoring multi-légions
- Tableau de bord central - vue d'ensemble de tous les navires de la flotte
- Enregistrement des légions - enregistrement automatique des nouveaux navires
- Health Checks - surveillance de la santé de chaque fort
- Fleet Analytics - analyses à l'échelle de la flotte
2. Suivi des erreurs en temps réel
- Agrégation des erreurs - collecte des erreurs de tous les navires
- Classification des erreurs - catégorisation des erreurs par type et par gravité
- Corrélation des erreurs - mise en relation des erreurs similaires
- Système d'alerte - notifications intelligentes
3. Monitoring des performances
- Métriques système - CPU, mémoire, disque, réseau
- Métriques applicatives - temps de réponse, débit, taux d'erreur
- Métriques métier - métriques personnalisées propres à la flotte
- Monitoring des SLA - suivi des objectifs de performance
4. Alerting avancé
- Smart Alerts - alertes basées sur le machine learning
- Escalation Policies - règles d'escalade des alertes
- Notifications multicanales - Slack, e-mail, SMS, webhook
- Corrélation des alertes - regroupement des alertes liées
� Architecture du système
1// Architecture Overview
2/*
3┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
4│ Legion Alpha │ │ Legion Beta │ │ Legion Gamma │
5│ (NestJS) │ │ (NestJS) │ │ (NestJS) │
6│ │ │ │ │ │
7│ - Error Agent │ │ - Error Agent │ │ - Error Agent │
8│ - Metrics Agent │ │ - Metrics Agent │ │ - Metrics Agent │
9│ - Health Check │ │ - Health Check │ │ - Health Check │
10└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
11 │ │ │
12 └──────────────────────┼──────────────────────┘
13 │
14 ┌─────────────┴─────────────┐
15 │ Fleet Monitor Center │
16 │ (Main NestJS App) │
17 │ │
18 │ - Data Aggregation │
19 │ - Alert Management │
20 │ - Dashboard API │
21 │ - Analytics Engine │
22 │ - Notification System │
23 └─────────────┬─────────────┘
24 │
25 ┌─────────────┴─────────────┐
26 │ External Systems │
27 │ │
28 │ - Prometheus/Grafana │
29 │ - Elasticsearch/Kibana │
30 │ - Slack/Teams │
31 │ - PagerDuty │
32 └───────────────────────────┘
33*/Implémentation du système
1. Agent de légion (pour chaque fort)
1// agents/cohort-agent.service.ts
2import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
3import { Cron } from '@nestjs/schedule';
4import { HttpService } from '@nestjs/axios';
5import { ConfigService } from '@nestjs/config';
6
7export interface LegionMetrics {
8 cohortId: string;
9 cohortName: string;
10 centurion: string;
11 timestamp: Date;
12 system: {
13 cpu: number;
14 memory: number;
15 disk: number;
16 network: {
17 bytesIn: number;
18 bytesOut: number;
19 };
20 };
21 application: {
22 uptime: number;
23 activeConnections: number;
24 requestsPerSecond: number;
25 errorRate: number;
26 responseTime: {
27 avg: number;
28 p95: number;
29 p99: number;
30 };
31 };
32 business: {
33 tributesFound: number;
34 legionActivity: number;
35 missionsCompleted: number;
36 battlesWon: number;
37 };
38 health: {
39 overall: 'healthy' | 'warning' | 'critical';
40 services: Record<string, 'up' | 'down' | 'degraded'>;
41 };
42}
43
44@Injectable()
45export class LegionAgentService implements OnModuleInit {
46 private readonly logger = new Logger(LegionAgentService.name);
47 private readonly cohortId: string;
48 private readonly fleetCenterUrl: string;
49 private isRegistered = false;
50
51 constructor(
52 private readonly httpService: HttpService,
53 private readonly configService: ConfigService,
54 ) {
55 this.cohortId = this.configService.get('SHIP_ID') || this.generateLegionId();
56 this.fleetCenterUrl = this.configService.get('FLEET_CENTER_URL');
57 }
58
59 async onModuleInit() {
60 await this.registerWithFleet();
61 }
62
63 private async registerWithFleet() {
64 try {
65 const registrationData = {
66 cohortId: this.cohortId,
67 cohortName: this.configService.get('SHIP_NAME', 'Unknown Vessel'),
68 centurion: this.configService.get('CAPTAIN_NAME', 'Anonymous Centurion'),
69 version: process.env.npm_package_version || '1.0.0',
70 startTime: new Date(),
71 location: {
72 latitude: parseFloat(this.configService.get('SHIP_LAT', '0')),
73 longitude: parseFloat(this.configService.get('SHIP_LNG', '0')),
74 },
75 };
76
77 await this.httpService.post(
78 `${this.fleetCenterUrl}/fleet/cohorts/register`,
79 registrationData
80 ).toPromise();
81
82 this.isRegistered = true;
83 this.logger.log(` Legion ${this.cohortId} registered with fleet center`);
84 } catch (error) {
85 this.logger.error('Failed to register with fleet center:', error.message);
86 }
87 }
88
89 @Cron('0 * * * * *') // Every minute
90 async sendMetricsToFleet() {
91 if (!this.isRegistered) {
92 await this.registerWithFleet();
93 return;
94 }
95
96 try {
97 const metrics = await this.collectLegionMetrics();
98
99 await this.httpService.post(
100 `${this.fleetCenterUrl}/fleet/metrics`,
101 metrics
102 ).toPromise();
103
104 this.logger.debug(` Metrics sent to fleet center`);
105 } catch (error) {
106 this.logger.error('Failed to send metrics to fleet center:', error.message);
107 }
108 }
109
110 async collectLegionMetrics(): Promise<LegionMetrics> {
111 const memUsage = process.memoryUsage();
112 const uptime = process.uptime();
113
114 return {
115 cohortId: this.cohortId,
116 cohortName: this.configService.get('SHIP_NAME', 'Unknown Vessel'),
117 centurion: this.configService.get('CAPTAIN_NAME', 'Anonymous Centurion'),
118 timestamp: new Date(),
119
120 system: {
121 cpu: await this.getCpuUsage(),
122 memory: Math.round((memUsage.heapUsed / memUsage.heapTotal) * 100),
123 disk: await this.getDiskUsage(),
124 network: await this.getNetworkStats(),
125 },
126
127 application: {
128 uptime,
129 activeConnections: await this.getActiveConnections(),
130 requestsPerSecond: await this.getRequestsPerSecond(),
131 errorRate: await this.getErrorRate(),
132 responseTime: await this.getResponseTimeStats(),
133 },
134
135 business: {
136 tributesFound: await this.getTributesFound(),
137 legionActivity: await this.getLegionActivity(),
138 missionsCompleted: await this.getMissionsCompleted(),
139 battlesWon: await this.getBattlesWon(),
140 },
141
142 health: await this.getHealthStatus(),
143 };
144 }
145
146 private async getCpuUsage(): Promise<number> {
147 return new Promise((resolve) => {
148 const startUsage = process.cpuUsage();
149 setTimeout(() => {
150 const endUsage = process.cpuUsage(startUsage);
151 const totalUsage = endUsage.user + endUsage.system;
152 const percentage = (totalUsage / 1000000) * 100;
153 resolve(Math.min(100, percentage));
154 }, 100);
155 });
156 }
157
158 private async getDiskUsage(): Promise<number> {
159 // Simplified disk usage - in real implementation use fs.statvfs
160 return Math.random() * 100;
161 }
162
163 private async getNetworkStats() {
164 // Simplified network stats - in real implementation read from /proc/net/dev
165 return {
166 bytesIn: Math.floor(Math.random() * 1000000),
167 bytesOut: Math.floor(Math.random() * 1000000),
168 };
169 }
170
171 private async getActiveConnections(): Promise<number> {
172 // Implementation depends on your server setup
173 return Math.floor(Math.random() * 100);
174 }
175
176 private async getRequestsPerSecond(): Promise<number> {
177 // Implementation using metrics collected by interceptors
178 return Math.random() * 1000;
179 }
180
181 private async getErrorRate(): Promise<number> {
182 // Implementation using error tracking
183 return Math.random() * 10;
184 }
185
186 private async getResponseTimeStats() {
187 return {
188 avg: 50 + Math.random() * 200,
189 p95: 100 + Math.random() * 400,
190 p99: 200 + Math.random() * 800,
191 };
192 }
193
194 private async getTributesFound(): Promise<number> {
195 // Business metric - implement based on your tribute service
196 return Math.floor(Math.random() * 50);
197 }
198
199 private async getLegionActivity(): Promise<number> {
200 // Business metric - implement based on your legion service
201 return Math.floor(Math.random() * 100);
202 }
203
204 private async getMissionsCompleted(): Promise<number> {
205 // Business metric - implement based on your mission service
206 return Math.floor(Math.random() * 20);
207 }
208
209 private async getBattlesWon(): Promise<number> {
210 // Business metric - implement based on your battle service
211 return Math.floor(Math.random() * 10);
212 }
213
214 private async getHealthStatus() {
215 // Implement comprehensive health checks
216 const services = {
217 database: Math.random() > 0.1 ? 'up' : 'down',
218 redis: Math.random() > 0.05 ? 'up' : 'down',
219 api: Math.random() > 0.02 ? 'up' : 'degraded',
220 auth: Math.random() > 0.01 ? 'up' : 'down',
221 };
222
223 const downServices = Object.values(services).filter(s => s === 'down').length;
224 const degradedServices = Object.values(services).filter(s => s === 'degraded').length;
225
226 let overall: 'healthy' | 'warning' | 'critical' = 'healthy';
227 if (downServices > 0) {
228 overall = 'critical';
229 } else if (degradedServices > 0) {
230 overall = 'warning';
231 }
232
233 return { overall, services };
234 }
235
236 private generateLegionId(): string {
237 return `cohort-${Math.random().toString(36).substr(2, 9)}`;
238 }
239
240 async sendErrorToFleet(error: any, context?: any) {
241 if (!this.isRegistered) return;
242
243 try {
244 const errorData = {
245 cohortId: this.cohortId,
246 timestamp: new Date(),
247 error: {
248 message: error.message,
249 stack: error.stack,
250 type: error.constructor.name,
251 code: error.code,
252 },
253 context: {
254 url: context?.url,
255 method: context?.method,
256 user: context?.user?.id,
257 requestId: context?.requestId,
258 ...context,
259 },
260 severity: this.classifyErrorSeverity(error),
261 };
262
263 await this.httpService.post(
264 `${this.fleetCenterUrl}/fleet/errors`,
265 errorData
266 ).toPromise();
267
268 this.logger.debug(' Error sent to fleet center');
269 } catch (err) {
270 this.logger.error('Failed to send error to fleet center:', err.message);
271 }
272 }
273
274 private classifyErrorSeverity(error: any): 'low' | 'medium' | 'high' | 'critical' {
275 if (error.name?.includes('Database') || error.code === 'ECONNREFUSED') {
276 return 'critical';
277 }
278 if (error.status >= 500) {
279 return 'high';
280 }
281 if (error.status >= 400) {
282 return 'medium';
283 }
284 return 'low';
285 }
286}2. Fleet Center - Hub central de monitoring
1// fleet-center/fleet-monitor.service.ts
2import { Injectable, Logger } from '@nestjs/common';
3import { EventEmitter2 } from '@nestjs/event-emitter';
4import { Cron } from '@nestjs/schedule';
5
6export interface Fleet {
7 cohorts: Map<string, LegionInfo>;
8 totalLegions: number;
9 healthyLegions: number;
10 warningLegions: number;
11 criticalLegions: number;
12 lastUpdate: Date;
13}
14
15export interface LegionInfo {
16 cohortId: string;
17 cohortName: string;
18 centurion: string;
19 lastSeen: Date;
20 metrics: LegionMetrics;
21 status: 'healthy' | 'warning' | 'critical' | 'offline';
22 alerts: Alert[];
23 errors: ErrorEvent[];
24}
25
26@Injectable()
27export class FleetMonitorService {
28 private readonly logger = new Logger(FleetMonitorService.name);
29 private fleet: Fleet = {
30 cohorts: new Map(),
31 totalLegions: 0,
32 healthyLegions: 0,
33 warningLegions: 0,
34 criticalLegions: 0,
35 lastUpdate: new Date(),
36 };
37
38 constructor(private readonly eventEmitter: EventEmitter2) {}
39
40 registerLegion(registrationData: any) {
41 const cohortInfo: LegionInfo = {
42 cohortId: registrationData.cohortId,
43 cohortName: registrationData.cohortName,
44 centurion: registrationData.centurion,
45 lastSeen: new Date(),
46 metrics: null,
47 status: 'healthy',
48 alerts: [],
49 errors: [],
50 };
51
52 this.fleet.cohorts.set(registrationData.cohortId, cohortInfo);
53 this.updateFleetStats();
54
55 this.logger.log(` Legion registered: ${cohortInfo.cohortName} (${cohortInfo.cohortId})`);
56
57 this.eventEmitter.emit('cohort.registered', cohortInfo);
58 }
59
60 updateLegionMetrics(cohortId: string, metrics: LegionMetrics) {
61 const cohort = this.fleet.cohorts.get(cohortId);
62 if (!cohort) {
63 this.logger.warn(`Received metrics from unregistered cohort: ${cohortId}`);
64 return;
65 }
66
67 cohort.metrics = metrics;
68 cohort.lastSeen = new Date();
69 cohort.status = this.determineLegionStatus(cohort);
70
71 this.updateFleetStats();
72 this.analyzeMetricsForAlerts(cohort);
73
74 this.eventEmitter.emit('cohort.metrics.updated', { cohortId, metrics });
75 }
76
77 recordLegionError(cohortId: string, errorData: any) {
78 const cohort = this.fleet.cohorts.get(cohortId);
79 if (!cohort) {
80 this.logger.warn(`Received error from unregistered cohort: ${cohortId}`);
81 return;
82 }
83
84 const errorEvent: ErrorEvent = {
85 id: `error-${Date.now()}-${Math.random()}`,
86 timestamp: new Date(),
87 cohortId,
88 ...errorData,
89 };
90
91 cohort.errors.push(errorEvent);
92
93 // Keep only last 100 errors per cohort
94 if (cohort.errors.length > 100) {
95 cohort.errors = cohort.errors.slice(-100);
96 }
97
98 this.processError(errorEvent);
99 this.eventEmitter.emit('cohort.error', errorEvent);
100 }
101
102 private determineLegionStatus(cohort: LegionInfo): 'healthy' | 'warning' | 'critical' | 'offline' {
103 if (!cohort.metrics) return 'offline';
104
105 const timeSinceLastSeen = Date.now() - cohort.lastSeen.getTime();
106 if (timeSinceLastSeen > 300000) { // 5 minutes
107 return 'offline';
108 }
109
110 if (cohort.metrics.health.overall === 'critical') {
111 return 'critical';
112 }
113
114 const { cpu, memory } = cohort.metrics.system;
115 const { errorRate } = cohort.metrics.application;
116
117 if (cpu > 90 || memory > 90 || errorRate > 10) {
118 return 'critical';
119 }
120
121 if (cpu > 70 || memory > 70 || errorRate > 5 || cohort.metrics.health.overall === 'warning') {
122 return 'warning';
123 }
124
125 return 'healthy';
126 }
127
128 private updateFleetStats() {
129 this.fleet.totalLegions = this.fleet.cohorts.size;
130 this.fleet.healthyLegions = 0;
131 this.fleet.warningLegions = 0;
132 this.fleet.criticalLegions = 0;
133
134 for (const cohort of this.fleet.cohorts.values()) {
135 switch (cohort.status) {
136 case 'healthy':
137 this.fleet.healthyLegions++;
138 break;
139 case 'warning':
140 this.fleet.warningLegions++;
141 break;
142 case 'critical':
143 this.fleet.criticalLegions++;
144 break;
145 }
146 }
147
148 this.fleet.lastUpdate = new Date();
149 }
150
151 private analyzeMetricsForAlerts(cohort: LegionInfo) {
152 const alerts: Alert[] = [];
153
154 // CPU Alert
155 if (cohort.metrics.system.cpu > 90) {
156 alerts.push(this.createAlert(
157 'high-cpu',
158 'critical',
159 `High CPU usage on ${cohort.cohortName}`,
160 `CPU usage is ${cohort.metrics.system.cpu.toFixed(1)}%`,
161 cohort.cohortId
162 ));
163 }
164
165 // Memory Alert
166 if (cohort.metrics.system.memory > 90) {
167 alerts.push(this.createAlert(
168 'high-memory',
169 'critical',
170 `High memory usage on ${cohort.cohortName}`,
171 `Memory usage is ${cohort.metrics.system.memory.toFixed(1)}%`,
172 cohort.cohortId
173 ));
174 }
175
176 // Error Rate Alert
177 if (cohort.metrics.application.errorRate > 10) {
178 alerts.push(this.createAlert(
179 'high-error-rate',
180 'high',
181 `High error rate on ${cohort.cohortName}`,
182 `Error rate is ${cohort.metrics.application.errorRate.toFixed(2)}%`,
183 cohort.cohortId
184 ));
185 }
186
187 // Response Time Alert
188 if (cohort.metrics.application.responseTime.p99 > 5000) {
189 alerts.push(this.createAlert(
190 'slow-response',
191 'medium',
192 `Slow response times on ${cohort.cohortName}`,
193 `P99 response time is ${cohort.metrics.application.responseTime.p99.toFixed(0)}ms`,
194 cohort.cohortId
195 ));
196 }
197
198 // Business Metrics Alerts
199 if (cohort.metrics.business.tributesFound === 0) {
200 alerts.push(this.createAlert(
201 'no-tributes',
202 'low',
203 `No tributes found on ${cohort.cohortName}`,
204 'Legion legion might need motivation!',
205 cohort.cohortId
206 ));
207 }
208
209 // Add new alerts and trigger them
210 for (const alert of alerts) {
211 if (!cohort.alerts.some(a => a.type === alert.type && a.severity === alert.severity)) {
212 cohort.alerts.push(alert);
213 this.eventEmitter.emit('alert.triggered', alert);
214 }
215 }
216 }
217
218 @Cron('0 */5 * * * *') // Every 5 minutes
219 checkFleetHealth() {
220 const offlineLegions = [];
221 const now = Date.now();
222
223 for (const [cohortId, cohort] of this.fleet.cohorts) {
224 const timeSinceLastSeen = now - cohort.lastSeen.getTime();
225
226 if (timeSinceLastSeen > 300000) { // 5 minutes offline
227 cohort.status = 'offline';
228 offlineLegions.push(cohort);
229 }
230 }
231
232 if (offlineLegions.length > 0) {
233 for (const cohort of offlineLegions) {
234 const alert = this.createAlert(
235 'cohort-offline',
236 'critical',
237 `Legion ${cohort.cohortName} is offline`,
238 `No communication for ${Math.round((now - cohort.lastSeen.getTime()) / 60000)} minutes`,
239 cohort.cohortId
240 );
241
242 this.eventEmitter.emit('alert.triggered', alert);
243 }
244 }
245
246 this.updateFleetStats();
247 }
248
249 getFleetOverview(): Fleet {
250 return this.fleet;
251 }
252
253 getLegionDetails(cohortId: string): LegionInfo | null {
254 return this.fleet.cohorts.get(cohortId) || null;
255 }
256
257 getFleetMetrics() {
258 const cohorts = Array.from(this.fleet.cohorts.values());
259
260 return {
261 fleet: this.fleet,
262 aggregatedMetrics: {
263 totalCpu: cohorts.reduce((sum, cohort) => sum + (cohort.metrics?.system.cpu || 0), 0),
264 totalMemory: cohorts.reduce((sum, cohort) => sum + (cohort.metrics?.system.memory || 0), 0),
265 totalErrors: cohorts.reduce((sum, cohort) => sum + cohort.errors.length, 0),
266 totalTributes: cohorts.reduce((sum, cohort) => sum + (cohort.metrics?.business.tributesFound || 0), 0),
267 averageResponseTime: cohorts.reduce((sum, cohort) => sum + (cohort.metrics?.application.responseTime.avg || 0), 0) / cohorts.length,
268 },
269 topPerformers: this.getTopPerformingLegions(),
270 worstPerformers: this.getWorstPerformingLegions(),
271 };
272 }
273
274 private getTopPerformingLegions(): LegionInfo[] {
275 return Array.from(this.fleet.cohorts.values())
276 .filter(cohort => cohort.metrics && cohort.status === 'healthy')
277 .sort((a, b) => b.metrics.business.tributesFound - a.metrics.business.tributesFound)
278 .slice(0, 5);
279 }
280
281 private getWorstPerformingLegions(): LegionInfo[] {
282 return Array.from(this.fleet.cohorts.values())
283 .filter(cohort => cohort.metrics && cohort.status !== 'healthy')
284 .sort((a, b) => b.metrics.application.errorRate - a.metrics.application.errorRate)
285 .slice(0, 5);
286 }
287
288 private createAlert(type: string, severity: string, title: string, description: string, cohortId: string): Alert {
289 return {
290 id: `alert-${Date.now()}-${Math.random()}`,
291 type,
292 severity,
293 title,
294 description,
295 cohortId,
296 timestamp: new Date(),
297 resolved: false,
298 };
299 }
300
301 private processError(errorEvent: ErrorEvent) {
302 // Implement error processing logic
303 // - Error correlation
304 // - Pattern detection
305 // - Automatic alert generation
306
307 this.logger.debug(`Processing error from ${errorEvent.cohortId}: ${errorEvent.error.message}`);
308 }
309}Tâches à réaliser
Partie 1 : Système de base (4-6 heures)
- Implémentation de l'agent de légion
- Implémenter LegionAgentService avec de vraies métriques
- Ajouter un intercepteur de suivi des erreurs
- Créer des endpoints de health check
- Ajouter l'enregistrement automatique auprès du fleet center
- Cœur du Fleet Center
- Implémenter FleetMonitorService
- Créer les endpoints d'API pour l'enregistrement et les métriques
- Ajouter un endpoint de tableau de bord de base
- Implémenter la communication WebSocket en temps réel
Partie 2 : Fonctionnalités avancées (6-8 heures)
- Alerting intelligent
- Machine learning pour la prédiction des problèmes
- Corrélation et déduplication des alertes
- Politiques d'escalade
- Notifications multicanales (Slack, e-mail, SMS)
- Moteur d'analytique
- Analyse de tendances et prévisions
- Détection d'anomalies
- Références de performance (baselines)
- Corrélation des métriques métier
Partie 3 : Fonctionnalités entreprise (8-10 heures)
- Monitoring avancé
- Framework de métriques personnalisées
- Traçage distribué
- Agrégation des logs
- Monitoring des SLA
- Intégration et déploiement
- Intégration Prometheus/Grafana
- Conteneurisation Docker
- Déploiement Kubernetes
- Pipeline CI/CD
Partie 4 : Tests et documentation (4-6 heures)
- Tests complets
- Tests unitaires pour tous les composants
- Tests d'intégration pour la communication de la flotte
- Tests de charge pour la scalabilité
- Tests E2E du workflow complet
- Documentation et guide de déploiement
- Documentation de l'API (Swagger)
- Guide de déploiement
- Runbooks de monitoring
- Guide de dépannage
Critères d'évaluation
� Critères d'excellence (90-100 %)
Implémentation complète de toutes les fonctionnalités requises Performance - le système gère >100 navires simultanément Scalabilité - ajout facile de nouveaux types de métriques et d'alertes Fiabilité - le système fonctionne de façon stable pendant 24 h sans interruption Sécurité - authentification et autorisation correctes Qualité du code - code propre, patterns appropriés, tests complets Documentation - documentation complète et guides de déploiement
Fonctionnalités avancées (points bonus)
Machine Learning - alerting prédictif et détection d'anomalies Auto-scaling - ajustement automatique des ressources en fonction des métriques Multi-région - prise en charge de flottes géographiquement distribuées Application mobile - application React Native pour le monitoring de la flotte Alertes vocales - intégration avec Amazon Alexa pour des notifications vocales
Bonne chance, amiral ! Ta flotte compte sur toi - crée un système de monitoring qui deviendra une légende des mers de la technologie !
Code de cette leçon : src/monitoring/fleet-monitoring-project.ts
1// PROJEKT: Kompletny System Monitorowania Floty
2// Combinaison d'Exception Filters, Logging, Health Checks et Alertes
3import { Injectable, Logger } from '@nestjs/common';
4import { Controller, Get } from '@nestjs/common';
5
6// ===========================================
7// 1. Système central de monitoring
8// ===========================================
9
10@Injectable()
11export class FleetMonitoringService {
12 private logger = new Logger('FleetMonitor');
13
14 // Registre des navires de la flotte
15 private ships = new Map<string, {
16 name: string;
17 status: 'operational' | 'warning' | 'critical' | 'offline';
18 lastHealthCheck: Date;
19 metrics: { cpu: number; memory: number; errors: number };
20 }>();
21
22 // Enregistrement d'un navire
23 registerShip(id: string, name: string) {
24 this.ships.set(id, {
25 name,
26 status: 'operational',
27 lastHealthCheck: new Date(),
28 metrics: { cpu: 0, memory: 0, errors: 0 },
29 });
30 this.logger.log('Zarejestrowano statek: ' + name);
31 }
32
33 // Mise à jour des métriques
34 updateMetrics(id: string, metrics: { cpu: number; memory: number; errors: number }) {
35 const ship = this.ships.get(id);
36 if (!ship) return;
37
38 ship.metrics = metrics;
39 ship.lastHealthCheck = new Date();
40
41 // Changement automatique du statut en fonction des métriques
42 if (metrics.errors > 10 || metrics.cpu > 95) {
43 ship.status = 'critical';
44 this.logger.error('CRITICAL: ' + ship.name);
45 } else if (metrics.errors > 5 || metrics.cpu > 80) {
46 ship.status = 'warning';
47 this.logger.warn('WARNING: ' + ship.name);
48 } else {
49 ship.status = 'operational';
50 }
51 }
52
53 // Tableau de bord de toute la flotte
54 getFleetDashboard() {
55 const ships = Array.from(this.ships.entries()).map(([id, data]) => ({
56 id, ...data,
57 }));
58
59 const operational = ships.filter(s => s.status === 'operational').length;
60 const warnings = ships.filter(s => s.status === 'warning').length;
61 const critical = ships.filter(s => s.status === 'critical').length;
62
63 return {
64 totalShips: ships.length,
65 operational,
66 warnings,
67 critical,
68 fleetHealth: critical > 0 ? 'ALERT' : warnings > 0 ? 'CAUTION' : 'OK',
69 ships,
70 generatedAt: new Date().toISOString(),
71 };
72 }
73}
74
75// ===========================================
76// 2. Dashboard Controller
77// ===========================================
78
79@Controller('fleet-monitor')
80export class FleetMonitorController {
81 constructor(private monitorService: FleetMonitoringService) {}
82
83 @Get('dashboard')
84 getDashboard() {
85 return this.monitorService.getFleetDashboard();
86 }
87}
88
89// ===========================================
90// 3. Simulation
91// ===========================================
92
93const monitor = new FleetMonitoringService();
94monitor.registerShip('S1', 'Imperator');
95monitor.registerShip('S2', 'Aquila');
96monitor.registerShip('S3', 'Victoria');
97
98monitor.updateMetrics('S1', { cpu: 45, memory: 60, errors: 0 });
99monitor.updateMetrics('S2', { cpu: 85, memory: 70, errors: 7 });
100monitor.updateMetrics('S3', { cpu: 20, memory: 30, errors: 0 });
101
102const dashboard = monitor.getFleetDashboard();
103console.log('=== Fleet Dashboard ===');
104console.log('Navires :', dashboard.totalShips);
105console.log('Operational:', dashboard.operational);
106console.log('Warnings:', dashboard.warnings);
107console.log('Critical:', dashboard.critical);
108console.log('État de la flotte :', dashboard.fleetHealth);
109Tu as repéré une erreur dans cette leçon ?