Dotarłeś do końcowego projektu tego modułu! Przez ostatnie lekcje poznałeś narzędzia, które są fundamentem profesjonalnego programowania: modularyzację kodu, techniki debugowania, profilowanie wydajności oraz zaawansowaną obsługę błędów. Teraz nadszedł czas, aby wszystkie te umiejętności połączyć w jeden kompleksowy system.
Wyobraź sobie, że po serii incydentów w Parku Jurajskim - awariach ogrodzeń, przeciążeniach systemów zasilania i niewyjaśnionych błędach w oprogramowaniu sterującym - zarząd parku postanowił stworzyć zaawansowany system diagnostyczny. System ten ma wykrywać problemy zanim staną się katastrofalne, logować wszystkie zdarzenia, a w przypadku awarii - dostarczać szczegółowe raporty, które pomogą inżynierom szybko zlokalizować i naprawić usterki. Twoje zadanie: zaprojektować i zaimplementować ten system od podstaw.
W tym projekcie zbudujesz modularny system diagnostyczny z własnymi typami błędów, zaawansowaną obsługą wyjątków, systemem logowania i narzędziami do analizy wydajności.
Stworzysz system diagnostyki Parku Jurajskiego składający się z czterech kluczowych modułów:
Pierwszym krokiem jest stworzenie dedykowanej hierarchii błędów, która pozwoli precyzyjnie identyfikować rodzaj i źródło każdego problemu w parku.
1// Bazowy błąd parku
2class ParkError extends Error {
3 constructor(message, code, sector, severity) {
4 super(message);
5 this.name = "ParkError";
6 this.code = code;
7 this.sector = sector;
8 this.severity = severity; // "low", "medium", "high", "critical"
9 this.timestamp = new Date().toISOString();
10 this.resolved = false;
11 }
12
13 resolve() {
14 this.resolved = true;
15 this.resolvedAt = new Date().toISOString();
16 }
17
18 toReport() {
19 return {
20 type: this.name,
21 message: this.message,
22 code: this.code,
23 sector: this.sector,
24 severity: this.severity,
25 timestamp: this.timestamp,
26 resolved: this.resolved
27 };
28 }
29}
30
31// Błąd ogrodzenia - krytyczny dla bezpieczeństwa
32class FenceError extends ParkError {
33 constructor(sector, fenceId, integrityLevel) {
34 const severity = integrityLevel < 50 ? "critical" : "high";
35 super(
36 `Ogrodzenie ${fenceId} w sektorze ${sector}: integralność ${integrityLevel}%`,
37 "FENCE_INTEGRITY",
38 sector,
39 severity
40 );
41 this.name = "FenceError";
42 this.fenceId = fenceId;
43 this.integrityLevel = integrityLevel;
44 }
45}
46
47// Błąd zasilania
48class PowerError extends ParkError {
49 constructor(sector, powerLevel, isBackupActive) {
50 const severity = !isBackupActive && powerLevel < 20 ? "critical" : "medium";
51 super(
52 `Zasilanie w sektorze ${sector}: ${powerLevel}% - backup: ${isBackupActive ? "aktywny" : "nieaktywny"}`,
53 "POWER_FAILURE",
54 sector,
55 severity
56 );
57 this.name = "PowerError";
58 this.powerLevel = powerLevel;
59 this.isBackupActive = isBackupActive;
60 }
61}
62
63// Błąd czujników
64class SensorError extends ParkError {
65 constructor(sector, sensorId, errorType) {
66 super(
67 `Czujnik ${sensorId} w sektorze ${sector}: ${errorType}`,
68 "SENSOR_MALFUNCTION",
69 sector,
70 "medium"
71 );
72 this.name = "SensorError";
73 this.sensorId = sensorId;
74 this.errorType = errorType;
75 }
76}
77
78// Błąd bezpieczeństwa - zawsze krytyczny
79class SecurityBreachError extends ParkError {
80 constructor(sector, breachType, speciesInvolved) {
81 super(
82 `NARUSZENIE BEZPIECZEŃSTWA w ${sector}: ${breachType} - gatunek: ${speciesInvolved}`,
83 "SECURITY_BREACH",
84 sector,
85 "critical"
86 );
87 this.name = "SecurityBreachError";
88 this.breachType = breachType;
89 this.speciesInvolved = speciesInvolved;
90 }
91}Każdy typ błędu niesie dodatkowe informacje kontekstowe, co ułatwia diagnostykę i naprawę problemu.
Drugi moduł to profesjonalny system logowania, który rejestruje wszystkie zdarzenia z odpowiednim poziomem ważności i kontekstem.
1// Poziomy logowania
2const LogLevel = {
3 DEBUG: 0,
4 INFO: 1,
5 WARN: 2,
6 ERROR: 3,
7 FATAL: 4
8};
9
10const LogLevelNames = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
11
12class ParkLogger {
13 constructor(moduleName, minLevel = LogLevel.DEBUG) {
14 this.moduleName = moduleName;
15 this.minLevel = minLevel;
16 this.logs = [];
17 this.maxLogs = 1000; // Limit przechowywanych logów
18 }
19
20 _log(level, message, context = {}) {
21 if (level < this.minLevel) return;
22
23 const entry = {
24 timestamp: new Date().toISOString(),
25 level: LogLevelNames[level],
26 module: this.moduleName,
27 message,
28 context
29 };
30
31 this.logs.push(entry);
32
33 // Rotacja logów - usuwanie najstarszych
34 if (this.logs.length > this.maxLogs) {
35 this.logs = this.logs.slice(-this.maxLogs);
36 }
37
38 // Formatowanie wyjścia
39 const prefix = `[${entry.timestamp}] [${entry.level}] [${this.moduleName}]`;
40 const contextStr = Object.keys(context).length > 0
41 ? ` | ${JSON.stringify(context)}`
42 : "";
43
44 switch (level) {
45 case LogLevel.DEBUG: console.log(`${prefix} ${message}${contextStr}`); break;
46 case LogLevel.INFO: console.info(`${prefix} ${message}${contextStr}`); break;
47 case LogLevel.WARN: console.warn(`${prefix} ${message}${contextStr}`); break;
48 case LogLevel.ERROR: console.error(`${prefix} ${message}${contextStr}`); break;
49 case LogLevel.FATAL: console.error(`🚨 ${prefix} ${message}${contextStr}`); break;
50 }
51
52 return entry;
53 }
54
55 debug(message, context) { return this._log(LogLevel.DEBUG, message, context); }
56 info(message, context) { return this._log(LogLevel.INFO, message, context); }
57 warn(message, context) { return this._log(LogLevel.WARN, message, context); }
58 error(message, context) { return this._log(LogLevel.ERROR, message, context); }
59 fatal(message, context) { return this._log(LogLevel.FATAL, message, context); }
60
61 // Filtrowanie logów po poziomie
62 getLogsByLevel(level) {
63 const levelName = LogLevelNames[level];
64 return this.logs.filter(log => log.level === levelName);
65 }
66
67 // Statystyki logów
68 getStats() {
69 const stats = { total: this.logs.length };
70 LogLevelNames.forEach(name => {
71 stats[name] = this.logs.filter(l => l.level === name).length;
72 });
73 return stats;
74 }
75}Logger pozwala na filtrowanie po poziomie, przechowuje historię zdarzeń i automatycznie rotuje stare logi.
Trzeci moduł to serce systemu - skanuje poszczególne podsystemy parku, przechwytuje i klasyfikuje błędy, a następnie podejmuje odpowiednie działania naprawcze.
1class ParkDiagnostics {
2 constructor() {
3 this.logger = new ParkLogger("Diagnostyka", LogLevel.DEBUG);
4 this.errors = [];
5 this.scanResults = [];
6 }
7
8 // Symulacja skanowania ogrodzenia
9 scanFence(sector, fenceId) {
10 this.logger.info(`Skanowanie ogrodzenia ${fenceId} w sektorze ${sector}`);
11
12 try {
13 const integrity = Math.round(40 + Math.random() * 60);
14
15 if (integrity < 80) {
16 throw new FenceError(sector, fenceId, integrity);
17 }
18
19 this.logger.debug(`Ogrodzenie ${fenceId} OK: ${integrity}%`, { sector, fenceId });
20 return { status: "OK", integrity };
21
22 } catch (error) {
23 if (error instanceof FenceError) {
24 this.errors.push(error);
25 this.logger.error(error.message, error.toReport());
26
27 if (error.severity === "critical") {
28 this.logger.fatal(`KRYTYCZNA awaria ogrodzenia! Ewakuacja sektora ${sector}!`);
29 this._initiateEmergencyProtocol(sector);
30 }
31 } else {
32 // Nieoczekiwany błąd - loguj i rzuć dalej
33 this.logger.fatal(`Nieoczekiwany błąd podczas skanowania: ${error.message}`);
34 throw error;
35 }
36
37 return { status: "AWARIA", error: error.toReport() };
38
39 } finally {
40 this.logger.debug(`Zakończono skanowanie ogrodzenia ${fenceId}`);
41 }
42 }
43
44 // Skanowanie zasilania z zagnieżdżonym try/catch
45 scanPower(sector) {
46 this.logger.info(`Skanowanie zasilania w sektorze ${sector}`);
47
48 try {
49 const mainPower = Math.round(Math.random() * 100);
50 let backupStatus = false;
51
52 try {
53 // Próba sprawdzenia zasilania awaryjnego
54 backupStatus = Math.random() > 0.3;
55 if (!backupStatus && mainPower < 30) {
56 throw new PowerError(sector, mainPower, false);
57 }
58 } catch (innerError) {
59 if (innerError instanceof PowerError) {
60 this.errors.push(innerError);
61 this.logger.error(`Problem z zasilaniem: ${innerError.message}`);
62 } else {
63 throw innerError; // Przekaż nieznane błędy do zewnętrznego catch
64 }
65 }
66
67 if (mainPower < 50) {
68 throw new PowerError(sector, mainPower, backupStatus);
69 }
70
71 return { status: "OK", mainPower, backupActive: backupStatus };
72
73 } catch (error) {
74 if (error instanceof PowerError) {
75 this.errors.push(error);
76 this.logger.warn(error.message, error.toReport());
77 return { status: "OSTRZEŻENIE", error: error.toReport() };
78 }
79 throw error;
80 }
81 }
82
83 // Pełna diagnostyka sektora
84 runFullDiagnostics(sector) {
85 this.logger.info(`=== Pełna diagnostyka sektora: ${sector} ===`);
86 const startTime = performance.now();
87
88 const result = {
89 sector,
90 fences: [],
91 power: null,
92 errors: [],
93 duration: 0
94 };
95
96 try {
97 // Skanowanie ogrodzeń (3 ogrodzenia per sektor)
98 for (let i = 1; i <= 3; i++) {
99 const fenceResult = this.scanFence(sector, `FENCE-${sector}-${i}`);
100 result.fences.push(fenceResult);
101 }
102
103 // Skanowanie zasilania
104 result.power = this.scanPower(sector);
105
106 } catch (unexpectedError) {
107 this.logger.fatal(`Nieoczekiwany błąd diagnostyki: ${unexpectedError.message}`);
108 result.criticalFailure = true;
109 } finally {
110 result.duration = `${(performance.now() - startTime).toFixed(2)}ms`;
111 result.errors = this.errors.filter(e => e.sector === sector).map(e => e.toReport());
112 this.scanResults.push(result);
113 this.logger.info(`Diagnostyka ${sector} zakończona w ${result.duration}`);
114 }
115
116 return result;
117 }
118
119 _initiateEmergencyProtocol(sector) {
120 this.logger.fatal(`🚨 PROTOKÓŁ AWARYJNY dla sektora ${sector} - wysyłanie powiadomień`);
121 }
122}Zwróć uwagę na zagnieżdżone bloki
try/catch i zastosowanie finally do rejestrowania czasu trwania operacji niezależnie od wyniku.Ostatni moduł zbiera dane ze wszystkich diagnostyk i generuje czytelny raport z rekomendacjami.
1class DiagnosticsReporter {
2 constructor(diagnostics) {
3 this.diagnostics = diagnostics;
4 this.logger = new ParkLogger("Raportowanie", LogLevel.INFO);
5 }
6
7 generateReport() {
8 const allErrors = this.diagnostics.errors;
9 const scanResults = this.diagnostics.scanResults;
10
11 // Grupowanie błędów po typie
12 const errorsByType = {};
13 allErrors.forEach(error => {
14 const type = error.name;
15 if (!errorsByType[type]) errorsByType[type] = [];
16 errorsByType[type].push(error.toReport());
17 });
18
19 // Grupowanie błędów po sektorze
20 const errorsBySector = {};
21 allErrors.forEach(error => {
22 if (!errorsBySector[error.sector]) errorsBySector[error.sector] = [];
23 errorsBySector[error.sector].push(error.toReport());
24 });
25
26 // Zliczanie po severity
27 const severityCounts = { low: 0, medium: 0, high: 0, critical: 0 };
28 allErrors.forEach(error => {
29 severityCounts[error.severity]++;
30 });
31
32 const report = {
33 generatedAt: new Date().toISOString(),
34 summary: {
35 totalErrors: allErrors.length,
36 criticalErrors: severityCounts.critical,
37 sectorsScanned: scanResults.length,
38 overallStatus: severityCounts.critical > 0 ? "KRYTYCZNY" :
39 severityCounts.high > 0 ? "OSTRZEŻENIE" : "STABILNY"
40 },
41 errorsByType,
42 errorsBySector,
43 severityCounts,
44 recommendations: this._generateRecommendations(allErrors, severityCounts)
45 };
46
47 this._printReport(report);
48 return report;
49 }
50
51 _generateRecommendations(errors, severity) {
52 const recommendations = [];
53
54 if (severity.critical > 0) {
55 recommendations.push("NATYCHMIAST: Ewakuacja sektorów z krytycznymi awariami ogrodzeń");
56 recommendations.push("Wysłanie zespołów naprawczych do uszkodzonych ogrodzeń");
57 }
58
59 if (severity.high > 0) {
60 recommendations.push("PILNE: Przegląd ogrodzeń z obniżoną integralnością");
61 }
62
63 const fenceErrors = errors.filter(e => e instanceof FenceError);
64 if (fenceErrors.length > 3) {
65 recommendations.push("Systemowy przegląd wszystkich ogrodzeń - wykryto liczne awarie");
66 }
67
68 const powerErrors = errors.filter(e => e instanceof PowerError);
69 if (powerErrors.length > 0) {
70 recommendations.push("Sprawdzenie generatorów awaryjnych we wszystkich sektorach");
71 }
72
73 if (recommendations.length === 0) {
74 recommendations.push("Wszystkie systemy stabilne - kontynuować standardowy monitoring");
75 }
76
77 return recommendations;
78 }
79
80 _printReport(report) {
81 console.log("\n" + "=".repeat(60));
82 console.log(" RAPORT DIAGNOSTYCZNY PARKU JURAJSKIEGO");
83 console.log("=".repeat(60));
84 console.log(`Wygenerowano: ${report.generatedAt}`);
85 console.log(`Status: ${report.summary.overallStatus}`);
86 console.log(`Sektory przeskanowane: ${report.summary.sectorsScanned}`);
87 console.log(`Wykryte błędy: ${report.summary.totalErrors} (krytycznych: ${report.summary.criticalErrors})`);
88
89 console.log("\nBłędy wg typu:");
90 Object.entries(report.errorsByType).forEach(([type, errors]) => {
91 console.log(` ${type}: ${errors.length}`);
92 });
93
94 console.log("\nRekomendacje:");
95 report.recommendations.forEach((rec, i) => {
96 console.log(` ${i + 1}. ${rec}`);
97 });
98
99 console.log("=".repeat(60));
100 }
101}
102
103// Uruchomienie systemu diagnostycznego
104const diagnostics = new ParkDiagnostics();
105
106const sectors = ["T-Rex-Paddock", "Raptor-Pen", "Herbivore-Valley"];
107sectors.forEach(sector => diagnostics.runFullDiagnostics(sector));
108
109const reporter = new DiagnosticsReporter(diagnostics);
110reporter.generateReport();
111
112// Statystyki loggera
113console.log("\nStatystyki logów:", diagnostics.logger.getStats());Przy budowaniu systemu diagnostycznego pamiętaj o następujących zasadach:
Hierarchia błędów powinna odzwierciedlać domenę:
1// Każdy typ błędu = jeden rodzaj problemu w parku
2ParkError // bazowy - ogólny błąd parku
3├── FenceError // ogrodzenia - bezpieczeństwo fizyczne
4├── PowerError // zasilanie - infrastruktura
5├── SensorError // czujniki - monitoring
6└── SecurityBreachError // naruszenia - najwyższy priorytetUżywaj instanceof do rozróżniania typów błędów:
1try {
2 riskyOperation();
3} catch (error) {
4 if (error instanceof SecurityBreachError) {
5 // Natychmiastowa reakcja
6 } else if (error instanceof FenceError) {
7 // Protokół naprawczy
8 } else if (error instanceof ParkError) {
9 // Ogólna obsługa błędów parku
10 } else {
11 // Nieznany błąd - loguj i eskaluj
12 throw error;
13 }
14}Zawsze używaj finally do czyszczenia zasobów:
1function scanWithCleanup() {
2 const connection = openConnection();
3 try {
4 return performScan(connection);
5 } catch (error) {
6 logError(error);
7 return null;
8 } finally {
9 // Zawsze zamknij połączenie - nawet przy błędzie
10 connection.close();
11 }
12}Ten projekt to Twoja szansa na udowodnienie, że potrafisz budować odporne, dobrze zorganizowane systemy. W Parku Jurajskim nie ma miejsca na nieobsłużone wyjątki - każdy błąd musi być przechwycony, zalogowany i obsłużony, zanim sytuacja wymknie się spod kontroli. Jak mówił John Hammond: "Nie szczędziliśmy kosztów!" - a Ty nie szczędź wysiłku przy obsłudze błędów.
Stwórz swój system diagnostyczny w edytorze poniżej. Zaimplementuj własne typy błędów, system logowania i mechanizm diagnostyki przynajmniej dwóch podsystemów parku.