JavaScript and TypeScript course Β· Module 5: Advanced JavaScript

Final Project: Jurassic Park Diagnostics System

9 min read
In this lesson4

Welcome to the final exercise of Module 5! We're going to build a complete Jurassic Park Diagnostics System that applies everything we've learned: custom error classes, a professional logging system, nested try/catch blocks, the finally clause, and modular code architecture.

"We need a system that can scan all park subsystems and generate a comprehensive diagnostic report," says Dr. Ellie Sattler. "Fences, power, sensors - and when something goes wrong, the system must log it, classify it by severity, and recommend a response."

System Architecture

The diagnostics system consists of four modules:

  1. Custom error hierarchy - ParkError, FenceError, PowerError, SensorError, SecurityBreachError
  2. Logging system - ParkLogger with log levels (DEBUG, INFO, WARN, ERROR, FATAL)
  3. Diagnostic module - ParkDiagnostics scanning subsystems with nested try/catch/finally
  4. Reporting module - DiagnosticsReporter aggregating results and generating recommendations

1. Custom Error Hierarchy

1// Base error for all park issues
2class ParkError extends Error {
3  constructor(message, code, sector, severity = "medium") {
4    super(message);
5    this.name = "ParkError";
6    this.code = code;
7    this.sector = sector;
8    this.severity = severity;
9    this.timestamp = new Date().toISOString();
10    this.resolved = false;
11
12    if (Error.captureStackTrace) {
13      Error.captureStackTrace(this, this.constructor);
14    }
15  }
16
17  resolve() {
18    this.resolved = true;
19    this.resolvedAt = new Date().toISOString();
20    return this;
21  }
22
23  toReport() {
24    return {
25      name: this.name,
26      message: this.message,
27      code: this.code,
28      sector: this.sector,
29      severity: this.severity,
30      timestamp: this.timestamp,
31      resolved: this.resolved
32    };
33  }
34}
35
36// Fence error - physical security
37class FenceError extends ParkError {
38  constructor(sector, fenceId, integrity) {
39    const severity = integrity < 50 ? "critical" : integrity < 70 ? "high" : "medium";
40    super(
41      `Fence failure ${fenceId} in sector ${sector}: integrity ${integrity}%`,
42      "FENCE_FAILURE",
43      sector,
44      severity
45    );
46    this.name = "FenceError";
47    this.fenceId = fenceId;
48    this.integrity = integrity;
49  }
50}
51
52// Power error - infrastructure
53class PowerError extends ParkError {
54  constructor(sector, powerLevel, backupAvailable) {
55    const severity = !backupAvailable && powerLevel < 30 ? "critical" : "high";
56    super(
57      `Power failure in sector ${sector}: ${powerLevel}%. Backup: ${backupAvailable ? "available" : "unavailable"}`,
58      "POWER_FAILURE",
59      sector,
60      severity
61    );
62    this.name = "PowerError";
63    this.powerLevel = powerLevel;
64    this.backupAvailable = backupAvailable;
65  }
66}
67
68// Sensor error
69class SensorError extends ParkError {
70  constructor(sector, sensorId, issue) {
71    super(
72      `Sensor ${sensorId} failure in sector ${sector}: ${issue}`,
73      "SENSOR_FAILURE",
74      sector,
75      "medium"
76    );
77    this.name = "SensorError";
78    this.sensorId = sensorId;
79    this.issue = issue;
80  }
81}
82
83// Security breach - always critical
84class SecurityBreachError extends ParkError {
85  constructor(sector, breachType, speciesInvolved) {
86    super(
87      `SECURITY BREACH in ${sector}: ${breachType} - species: ${speciesInvolved}`,
88      "SECURITY_BREACH",
89      sector,
90      "critical"
91    );
92    this.name = "SecurityBreachError";
93    this.breachType = breachType;
94    this.speciesInvolved = speciesInvolved;
95  }
96}

Each error type carries additional contextual information, making diagnosis and repair easier.

2. Logging System (Logger with Levels)

The second module is a professional logging system that records all events with appropriate severity and context.

1// Log levels
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; // Stored log limit
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    // Log rotation - remove oldest
34    if (this.logs.length > this.maxLogs) {
35      this.logs = this.logs.slice(-this.maxLogs);
36    }
37
38    // Format output
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  // Filter logs by level
62  getLogsByLevel(level) {
63    const levelName = LogLevelNames[level];
64    return this.logs.filter(log => log.level === levelName);
65  }
66
67  // Log statistics
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}

The logger supports level filtering, stores event history, and automatically rotates old logs.

3. Diagnostic Module (Scanning and try/catch/finally)

The third module is the heart of the system - it scans individual park subsystems, catches and classifies errors, then takes appropriate corrective actions.

1class ParkDiagnostics {
2  constructor() {
3    this.logger = new ParkLogger("Diagnostics", LogLevel.DEBUG);
4    this.errors = [];
5    this.scanResults = [];
6  }
7
8  // Simulate fence scanning
9  scanFence(sector, fenceId) {
10    this.logger.info(`Scanning fence ${fenceId} in sector ${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(`Fence ${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(`CRITICAL fence failure! Evacuate sector ${sector}!`);
29          this._initiateEmergencyProtocol(sector);
30        }
31      } else {
32        // Unexpected error - log and re-throw
33        this.logger.fatal(`Unexpected error during scan: ${error.message}`);
34        throw error;
35      }
36
37      return { status: "FAILURE", error: error.toReport() };
38
39    } finally {
40      this.logger.debug(`Fence ${fenceId} scan completed`);
41    }
42  }
43
44  // Power scanning with nested try/catch
45  scanPower(sector) {
46    this.logger.info(`Scanning power in sector ${sector}`);
47
48    try {
49      const mainPower = Math.round(Math.random() * 100);
50      let backupStatus = false;
51
52      try {
53        // Attempt to check backup power
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(`Power issue: ${innerError.message}`);
62        } else {
63          throw innerError; // Pass unknown errors to outer 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: "WARNING", error: error.toReport() };
78      }
79      throw error;
80    }
81  }
82
83  // Full sector diagnostics
84  runFullDiagnostics(sector) {
85    this.logger.info(`=== Full diagnostics for sector: ${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      // Scan fences (3 fences per sector)
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      // Scan power
104      result.power = this.scanPower(sector);
105
106    } catch (unexpectedError) {
107      this.logger.fatal(`Unexpected diagnostics error: ${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(`Diagnostics ${sector} completed in ${result.duration}`);
114    }
115
116    return result;
117  }
118
119  _initiateEmergencyProtocol(sector) {
120    this.logger.fatal(`EMERGENCY PROTOCOL for sector ${sector} - sending notifications`);
121  }
122}

Note the nested try/catch blocks and the use of finally to record operation duration regardless of outcome.

4. Reporting Module (Data Aggregation and Analysis)

The last module collects data from all diagnostics and generates a readable report with recommendations.

1class DiagnosticsReporter {
2  constructor(diagnostics) {
3    this.diagnostics = diagnostics;
4    this.logger = new ParkLogger("Reporting", LogLevel.INFO);
5  }
6
7  generateReport() {
8    const allErrors = this.diagnostics.errors;
9    const scanResults = this.diagnostics.scanResults;
10
11    // Group errors by type
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    // Group errors by sector
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    // Count by 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 ? "CRITICAL" :
39                       severityCounts.high > 0 ? "WARNING" : "STABLE"
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("IMMEDIATELY: Evacuate sectors with critical fence failures");
56      recommendations.push("Deploy repair teams to damaged fences");
57    }
58
59    if (severity.high > 0) {
60      recommendations.push("URGENT: Review fences with reduced integrity");
61    }
62
63    const fenceErrors = errors.filter(e => e instanceof FenceError);
64    if (fenceErrors.length > 3) {
65      recommendations.push("Systematic review of all fences - multiple failures detected");
66    }
67
68    const powerErrors = errors.filter(e => e instanceof PowerError);
69    if (powerErrors.length > 0) {
70      recommendations.push("Check backup generators in all sectors");
71    }
72
73    if (recommendations.length === 0) {
74      recommendations.push("All systems stable - continue standard monitoring");
75    }
76
77    return recommendations;
78  }
79
80  _printReport(report) {
81    console.log("\n" + "=".repeat(60));
82    console.log("   JURASSIC PARK DIAGNOSTIC REPORT");
83    console.log("=".repeat(60));
84    console.log(`Generated: ${report.generatedAt}`);
85    console.log(`Status: ${report.summary.overallStatus}`);
86    console.log(`Sectors scanned: ${report.summary.sectorsScanned}`);
87    console.log(`Errors detected: ${report.summary.totalErrors} (critical: ${report.summary.criticalErrors})`);
88
89    console.log("\nErrors by type:");
90    Object.entries(report.errorsByType).forEach(([type, errors]) => {
91      console.log(`  ${type}: ${errors.length}`);
92    });
93
94    console.log("\nRecommendations:");
95    report.recommendations.forEach((rec, i) => {
96      console.log(`  ${i + 1}. ${rec}`);
97    });
98
99    console.log("=".repeat(60));
100  }
101}
102
103// Run the diagnostic system
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// Logger statistics
113console.log("\nLog statistics:", diagnostics.logger.getStats());

Implementation Tips

When building the diagnostic system, keep these principles in mind:

The error hierarchy should reflect the domain:

1// Each error type = one kind of park problem
2ParkError          // base - general park error
3β”œβ”€β”€ FenceError     // fences - physical security
4β”œβ”€β”€ PowerError     // power - infrastructure
5β”œβ”€β”€ SensorError    // sensors - monitoring
6└── SecurityBreachError  // breaches - highest priority

Use instanceof to distinguish error types:

1try {
2  riskyOperation();
3} catch (error) {
4  if (error instanceof SecurityBreachError) {
5    // Immediate response
6  } else if (error instanceof FenceError) {
7    // Repair protocol
8  } else if (error instanceof ParkError) {
9    // General park error handling
10  } else {
11    // Unknown error - log and escalate
12    throw error;
13  }
14}

Always use finally for cleanup:

1function scanWithCleanup() {
2  const connection = openConnection();
3  try {
4    return performScan(connection);
5  } catch (error) {
6    logError(error);
7    return null;
8  } finally {
9    // Always close the connection - even on error
10    connection.close();
11  }
12}

Good Luck!

This project is your chance to prove you can build resilient, well-organized systems. In Jurassic Park there's no room for unhandled exceptions - every error must be caught, logged, and handled before the situation gets out of control. As John Hammond said: "We spared no expense!" - and you shouldn't spare effort when it comes to error handling.

Create your diagnostic system in the editor below. Implement your own error types, logging system, and a diagnostic mechanism for at least two park subsystems.

Code for this lesson: index.js
1// Final project: Jurassic Park diagnostics system
2// Combine: modules, error handling, try/catch, custom errors
3
4// === 1. ERROR HIERARCHY ===
5// TODO: Create a class ParkError that extends Error
6class ParkError extends Error {
7  constructor(message, code, sector, severity) {
8    super(message);
9    this.name = "ParkError";
10    // TODO: Assign code, sector, severity, timestamp
11  }
12
13  // TODO: A method toReport() that returns a report object
14}
15
16// TODO: Create the derived classes: FenceError, PowerError, SystemError
17class FenceError extends ParkError {
18  constructor(message, sector, fenceId) {
19    // TODO: Call super with the right parameters
20  }
21}
22
23// === 2. LOGGING SYSTEM ===
24// TODO: Create a Logger object with the levels: debug, info, warn, error
25const Logger = {
26  logs: [],
27  // TODO: Methods log(level, message, context)
28  // TODO: Method getLogsByLevel(level)
29  // TODO: Method exportLogs()
30};
31
32// === 3. DIAGNOSTICS MODULE ===
33// TODO: Create diagnostic functions that use try/catch/finally
34function scanSubsystem(subsystemName) {
35  // TODO: Simulate scanning the subsystem
36  // TODO: Use try/catch to catch errors
37  // TODO: Use finally to log the end of the scan
38}
39
40// === 4. REPORT GENERATOR ===
41// TODO: Create a function generateDiagnosticReport()
42// Collect data from all subsystems
43// Handle each subsystem's errors separately
44
45function generateDiagnosticReport() {
46  console.log("=== DIAGNOSTIC REPORT ===");
47  const subsystems = ["Fences", "Power", "Monitoring", "Communication"];
48  // TODO: Scan each subsystem with error handling
49  console.log("=== END OF REPORT ===");
50}
51
52// Run the diagnostics:
53generateDiagnosticReport();

Spotted a mistake in this lesson?

Useful articles