Skip to content

Feature/health check plugin + Metric Plugin - #105

Merged
fmazmz merged 12 commits into
mainfrom
feature/health-check-plugin
Feb 22, 2026
Merged

Feature/health check plugin + Metric Plugin#105
fmazmz merged 12 commits into
mainfrom
feature/health-check-plugin

Conversation

@bamsemats

@bamsemats bamsemats commented Feb 20, 2026

Copy link
Copy Markdown

Closes #104
Closes #115

This PR introduces the HealthCheckPlugin and MetricPlugin, providing a standardized JSON health check endpoint at /health - this is the first functional plugin added to our server's architecture, demonstrating how to handle dynamic JSON responses and route-specific plugin registration.

Additionally, another plugin (MetricPlugin) is implemented, which checks and returns data on:

Current timestamp (with timezone)
Application version
Git commit hash
Server uptime / response time
JVM memory usage (used / max)

Summary by CodeRabbit

  • New Features

    • Added health endpoint (/health) and metrics endpoint (/metric) returning JSON with status, timestamps, version, commit, response time, and memory.
    • Added a live health dashboard in the top-right of the UI that refreshes every second.
  • Chores

    • Embedded build metadata (version, commit, time) into the app for display.
  • Tests

    • Added unit tests validating metric endpoint responses.

@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds HealthCheckPlugin and MetricPlugin endpoints and registers them; augments HttpRequest with creationTimeNanos; introduces frontend health UI (HTML/CSS/JS) that polls /metric; adds build-time filtering and build.properties; includes a unit test for MetricPlugin.

Changes

Cohort / File(s) Summary
Plugin Implementations
src/main/java/org/juv25d/plugin/HealthCheckPlugin.java, src/main/java/org/juv25d/plugin/MetricPlugin.java
New plugins: HealthCheckPlugin returns {"status":"UP"}; MetricPlugin returns JSON with local/UTC timestamps, server name, buildVersion, gitCommit, responseTimeUs (uses HttpRequest.creationTimeNanos), and JVM memory info.
Core App & HTTP Model
src/main/java/org/juv25d/App.java, src/main/java/org/juv25d/http/HttpRequest.java
Registers /health and /metric endpoints; HttpRequest record gains creationTimeNanos field, a new canonical constructor param, and a convenience ctor that sets it to System.nanoTime().
Frontend Assets
src/main/resources/static/index.html, src/main/resources/static/css/styles.css, src/main/resources/static/js/metric.js
Adds a health-box UI and popover; CSS for positioning/animation; metric.js polls /metric every second and updates DOM elements with metrics.
Build & Resources
pom.xml, build.properties, src/main/resources-filtered/build.properties
Adds SCM metadata, build.time property, buildnumber-maven-plugin config, enables resource filtering and a filtered build.properties with ${project.version}, ${buildNumber}, ${build.time}.
Tests
src/test/java/org/juv25d/plugin/MetricPluginTest.java
New unit test asserting MetricPlugin returns 200, application/json, contains expected JSON fields, and includes correct Content-Length.

Sequence Diagram

sequenceDiagram
    participant Client as Browser Client
    participant Server as HTTP Server
    participant MetricPlugin as MetricPlugin
    participant System as JVM/System

    Client->>Server: GET /metric
    Server->>MetricPlugin: handle(request, response)
    MetricPlugin->>System: read build.properties
    MetricPlugin->>System: Runtime.getRuntime() (memory)
    MetricPlugin->>System: System.nanoTime() (now)
    MetricPlugin->>MetricPlugin: compute responseTimeUs (now - request.creationTimeNanos)
    MetricPlugin->>Server: set 200, headers, JSON body
    Server->>Client: HTTP 200 + JSON (metrics)
    Client->>Client: parse JSON & update DOM
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~27 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • fmazmz
  • TatjanaTrajkovic

Poem

🐰
I hopped to the server with bytes in my paw,
Brought times and commits and memory to draw.
A heartbeat in JSON, a metric so bright,
I nibble on headers and dance through the night.
Build stamped and buzzing — the webserver's all right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive Build configuration changes (pom.xml, build.properties, src/main/resources-filtered/) and UI enhancements (CSS, HTML, JavaScript) support the core plugin functionality but are not explicitly required by issues #104 and #115. Clarify if build metadata filtering and health status UI components are necessary infrastructure for the plugins or represent scope creep; consider separating UI/build enhancements into a follow-up PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/health check plugin + Metric Plugin' directly references both main features implemented in the PR (HealthCheckPlugin and MetricPlugin) and accurately summarizes the primary changes.
Linked Issues check ✅ Passed The PR successfully implements both linked issues: HealthCheckPlugin with minimal JSON response at /health [#104] and MetricPlugin with structured JSON containing timestamp, version, commit, response time, and memory usage at /metric [#115].

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/health-check-plugin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/main/java/org/juv25d/plugin/HealthCheckPlugin.java (2)

27-28: Compute the UTF-8 bytes once and reuse.

getBytes(StandardCharsets.UTF_8) is called twice — once to measure the length and again to set the body — resulting in two separate encode operations and two heap allocations. Compute it once and reference the array for both the Content-Length header and setBody.

♻️ Proposed refactor
-        res.setHeader("Content-Length", String.valueOf(jsonBody.getBytes(StandardCharsets.UTF_8).length));
-        res.setBody(jsonBody.getBytes(StandardCharsets.UTF_8));
+        byte[] bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8);
+        res.setHeader("Content-Length", String.valueOf(bodyBytes.length));
+        res.setBody(bodyBytes);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 27 - 28,
The code calls jsonBody.getBytes(StandardCharsets.UTF_8) twice causing two
encodings and allocations; compute the UTF-8 byte[] once (e.g., byte[] bodyBytes
= jsonBody.getBytes(StandardCharsets.UTF_8)) and reuse that array for both
res.setHeader("Content-Length", String.valueOf(bodyBytes.length)) and
res.setBody(bodyBytes) inside the HealthCheckPlugin response handling so the
header length and body come from the same bytes.

19-22: Extract the hardcoded server version string to a named constant.

The inline literal "juv25d-webserver/1.0" will need to be updated in sync with any future versioning change. A static constant is easier to maintain and update.

♻️ Proposed refactor
 public class HealthCheckPlugin implements Plugin {
+    private static final String SERVER_NAME = "juv25d-webserver/1.0";
 
     `@Override`
     public void handle(HttpRequest req, HttpResponse res) throws IOException {
         String timestamp = Instant.now().toString();
         String jsonBody = String.format(
-            "{\"status\": \"UP\", \"timestamp\": \"%s\", \"server\": \"juv25d-webserver/1.0\"}",
+            "{\"status\": \"UP\", \"timestamp\": \"%s\", \"server\": \"%s\"}",
-            timestamp
+            timestamp, SERVER_NAME
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 19 - 22,
Replace the hardcoded server version literal in the JSON body with a named
constant: add a private static final String SERVER_VERSION =
"juv25d-webserver/1.0" to the HealthCheckPlugin class and update the
String.format call (in the method that builds jsonBody) to use SERVER_VERSION
instead of the inline literal; ensure any other occurrences in HealthCheckPlugin
reference this constant so future version changes require only one edit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 27-28: The code calls jsonBody.getBytes(StandardCharsets.UTF_8)
twice causing two encodings and allocations; compute the UTF-8 byte[] once
(e.g., byte[] bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8)) and reuse
that array for both res.setHeader("Content-Length",
String.valueOf(bodyBytes.length)) and res.setBody(bodyBytes) inside the
HealthCheckPlugin response handling so the header length and body come from the
same bytes.
- Around line 19-22: Replace the hardcoded server version literal in the JSON
body with a named constant: add a private static final String SERVER_VERSION =
"juv25d-webserver/1.0" to the HealthCheckPlugin class and update the
String.format call (in the method that builds jsonBody) to use SERVER_VERSION
instead of the inline literal; ensure any other occurrences in HealthCheckPlugin
reference this constant so future version changes require only one edit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/main/java/org/juv25d/plugin/HealthCheckPlugin.java (3)

28-28: Consider declaring the charset in the Content-Type header.

application/json is RFC 8259-compliant without a charset parameter (UTF-8 is implied), so this is not a bug. Adding ; charset=utf-8 makes the encoding contract explicit for clients that do not implement the RFC default.

♻️ Proposed change
-        res.setHeader("Content-Type", "application/json");
+        res.setHeader("Content-Type", "application/json; charset=utf-8");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` at line 28, The
Content-Type header set in HealthCheckPlugin currently uses "application/json"
without an explicit charset; update the response header in the code that calls
res.setHeader to include the charset (e.g., "application/json; charset=utf-8")
so clients receive an explicit UTF-8 encoding contract—locate the res.setHeader
call in HealthCheckPlugin and change its value accordingly.

20-23: Manual JSON construction has no escaping — fragile for future extension.

Instant.now().toString() and the hardcoded SERVER_NAME are both free of JSON-unsafe characters today, so there is no immediate bug. However, String.format performs zero JSON escaping; any future field sourced from dynamic input (e.g., a host name, error message, or environment variable) will silently produce malformed or injectable JSON.

Since the project intentionally avoids external dependencies, consider a small dedicated helper, or at minimum document the no-escaping assumption in a comment so future contributors don't add dynamic fields without noticing the risk.

♻️ Proposed minimal safe pattern (stdlib only)
-        String jsonBody = String.format(
-            "{\"status\": \"UP\", \"timestamp\": \"%s\", \"server\": \"%s\"}",
-            timestamp, SERVER_NAME
-        );
+        // Values are controlled (ISO-8601 instant + hardcoded literal) — no escaping needed.
+        // If any dynamic field is added, escape it with jsonEscape() first.
+        String jsonBody = "{\"status\": \"UP\", \"timestamp\": \"" + timestamp
+                + "\", \"server\": \"" + SERVER_NAME + "\"}";

Or, if a lightweight JSON dependency is ever introduced (e.g., Jackson, Gson, or org.json), replace the entire block with a proper object-to-JSON serialisation call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 20 - 23,
The JSON body is built with String.format without escaping, which will break if
timestamp or SERVER_NAME become JSON-unsafe; add a small stdlib JSON-escaping
helper (e.g., a private static String escapeJson(String s) in HealthCheckPlugin)
and use it when constructing jsonBody (escape timestamp and SERVER_NAME) instead
of raw %s, or alternatively add a clear comment above the jsonBody construction
that documents the no-escaping assumption; reference jsonBody, SERVER_NAME,
timestamp and the new escapeJson helper to locate the change.

25-26: Redundant status-code and status-text setters.

HttpResponse's no-arg constructor already initialises statusCode = 200 and statusText = "OK" (see HttpResponse.java, lines 16–19). Explicitly re-setting them here adds noise without effect.

♻️ Proposed cleanup
-        res.setStatusCode(200);
-        res.setStatusText("OK");
         byte[] bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 25 - 26,
In HealthCheckPlugin.java remove the redundant explicit settings of the default
HTTP status in the method that builds the response (the two calls
res.setStatusCode(200) and res.setStatusText("OK")); HttpResponse's no-arg
constructor already initializes statusCode to 200 and statusText to "OK", so
simply return the constructed HttpResponse instance (created via new
HttpResponse()) without re-setting those fields to avoid noise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Line 28: The Content-Type header set in HealthCheckPlugin currently uses
"application/json" without an explicit charset; update the response header in
the code that calls res.setHeader to include the charset (e.g.,
"application/json; charset=utf-8") so clients receive an explicit UTF-8 encoding
contract—locate the res.setHeader call in HealthCheckPlugin and change its value
accordingly.
- Around line 20-23: The JSON body is built with String.format without escaping,
which will break if timestamp or SERVER_NAME become JSON-unsafe; add a small
stdlib JSON-escaping helper (e.g., a private static String escapeJson(String s)
in HealthCheckPlugin) and use it when constructing jsonBody (escape timestamp
and SERVER_NAME) instead of raw %s, or alternatively add a clear comment above
the jsonBody construction that documents the no-escaping assumption; reference
jsonBody, SERVER_NAME, timestamp and the new escapeJson helper to locate the
change.
- Around line 25-26: In HealthCheckPlugin.java remove the redundant explicit
settings of the default HTTP status in the method that builds the response (the
two calls res.setStatusCode(200) and res.setStatusText("OK")); HttpResponse's
no-arg constructor already initializes statusCode to 200 and statusText to "OK",
so simply return the constructed HttpResponse instance (created via new
HttpResponse()) without re-setting those fields to avoid noise.

…gin responses

Integrate `build.properties` for versioning and metadata. Enhance `/health` endpoint with response time, memory usage, and build details. Add UI widget for health status and dynamic frontend updates.
…gin responses

Integrate `build.properties` for versioning and metadata. Enhance `/health` endpoint with response time, memory usage, and build details. Add UI widget for health status and dynamic frontend updates.
Refine response time calculations from milliseconds to microseconds. Update button interaction styles and enhance accuracy of frontend health status presentation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/main/resources/static/js/health.js (2)

25-25: 1-second polling is aggressive for a status widget.

Each open browser tab generates 3,600 requests/hour. A 5–10 second interval would meaningfully reduce server load while still providing timely updates.

♻️ Proposed change
-setInterval(loadHealth, 1_000);
+setInterval(loadHealth, 10_000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/health.js` at line 25, The polling interval for
the status widget is too aggressive: replace the current setInterval call that
invokes loadHealth every 1_000 ms with a less frequent interval (e.g.,
5_000–10_000 ms) to reduce server load; update the setInterval(loadHealth,
1_000) invocation in the file so it uses a 5000 (or 10000) millisecond interval
instead while keeping the same loadHealth function and semantics.

19-21: Error handler leaves stale values in time/version/commit fields.

After a successful load followed by a fetch failure, health-status switches to DOWN but local-time, utc-time, health-version, etc. retain their last known values. This presents a mixed/misleading state.

♻️ Proposed fix: clear all fields on error
     } catch (e) {
         document.getElementById("health-status").textContent = "DOWN";
+        document.getElementById("local-time").textContent = "";
+        document.getElementById("utc-time").textContent = "";
+        document.getElementById("health-version").textContent = "";
+        document.getElementById("health-commit").textContent = "";
+        document.getElementById("health-response").textContent = "";
+        document.getElementById("health-memory").textContent = "";
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/health.js` around lines 19 - 21, The catch block
that currently only sets document.getElementById("health-status").textContent =
"DOWN" leaves stale values in other fields; update the error handler in
health.js to also clear or set to a neutral value (e.g., empty string or "N/A")
the other DOM nodes such as "local-time", "utc-time", "health-version",
"health-commit" (or any similar IDs used for time/version/commit) so that when
the fetch fails all related fields are reset instead of showing outdated data.
pom.xml (1)

229-244: buildnumber-maven-plugin in <pluginManagement> is redundant and missing its <configuration>.

The plugin is already fully declared and configured in <plugins> (lines 187–204). In a single-module project, <pluginManagement> entries are not executed independently — they only provide defaults to be inherited. Having the same plugin in both sections with a partial configuration (no doCheck/doUpdate/shortRevisionLength) creates a maintenance hazard if the <plugins> entry is ever removed.

🧹 Proposed fix: remove the redundant pluginManagement block
-        <pluginManagement>
-            <plugins>
-                <plugin>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>buildnumber-maven-plugin</artifactId>
-                    <version>3.2.0</version>
-                    <executions>
-                        <execution>
-                            <phase>validate</phase>
-                            <goals>
-                                <goal>create</goal>
-                            </goals>
-                        </execution>
-                    </executions>
-                </plugin>
-            </plugins>
-        </pluginManagement>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pom.xml` around lines 229 - 244, The POM contains a redundant
buildnumber-maven-plugin entry under pluginManagement that is incomplete
(missing configuration like doCheck, doUpdate, shortRevisionLength) while a full
declaration already exists under plugins; remove the plugin element from the
pluginManagement section so there is a single authoritative declaration of
org.codehaus.mojo:buildnumber-maven-plugin (the entry with execution goal
"create" in pluginManagement) and rely on the fully-configured plugin in the
<plugins> section to avoid configuration drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pom.xml`:
- Around line 246-251: The resources block in pom.xml currently sets filtering
true for the entire src/main/resources which causes Maven to attempt property
substitution in all files (including static JS like health.js) and can corrupt
binaries; change the Maven resource configuration to only filter a dedicated
filtered resource directory (e.g., src/main/resources-filtered) containing
build.properties and leave src/main/resources unfiltered, then move
build.properties into src/main/resources-filtered and update the <resources> and
<filteredResources> (or separate <resource> entries) so only the filtered
directory has <filtering>true</filtering>.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 51-74: The JSON string built in HealthCheckPlugin (the multi-line
String.format block that interpolates localTime, utcTime, SERVER_NAME, version,
commit, responseTimeMs, usedMemory, maxMemory) injects version and commit raw,
risking broken JSON if they contain " or \; update the code to escape those
fields before interpolation (e.g., replace backslashes and quotes or better:
construct the response with a JSON builder/mapper such as Jackson/Gson or
org.json to serialize a Map/object) so version and commit are safely serialized
into the JSON output.
- Around line 27-37: The build.properties loading is happening per-request; move
that I/O into a one-time initialization by reading build.properties in the
HealthCheckPlugin constructor or a static initializer and storing the values in
final fields (e.g. private final String version; private final String commit)
instead of reloading the Properties on each /health request; update the request
handler (the method that currently reads props) to simply use these cached
version and commit fields.

In `@src/main/resources/static/css/styles.css`:
- Around line 43-78: Remove the blank lines that trigger Stylelint's
declaration-empty-line-before rule: in the `#health-box` button block (referenced
by selector "#health-box button") delete the empty line immediately before the
"transition" declaration so it directly follows the preceding rule, and in the
.health-content block (selector ".health-content") delete the empty line
immediately before the "position: absolute" declaration so that property sits
directly after the preceding declaration; preserve existing indentation and
spacing for other properties.

In `@src/main/resources/static/index.html`:
- Line 11: The button element with content "🩺" (the health button) is missing
an aria-label for screen readers; update the <button
popovertarget="health-content"> element to include a descriptive aria-label (for
example aria-label="Show health panel" or "Open health status") so that
assistive technologies convey the button's action clearly.

In `@src/main/resources/static/js/health.js`:
- Around line 3-4: The fetch result handling in health.js must guard the
response before calling res.json(): check res.ok after const res = await
fetch(...) and handle non-2xx responses (e.g., log or read an error body and set
the health state to DOWN) instead of unconditionally calling const data = await
res.json(); update the code around the res and data variables so non-ok
responses are parsed/treated as errors and only successful responses are passed
to the existing health payload handling logic.

---

Nitpick comments:
In `@pom.xml`:
- Around line 229-244: The POM contains a redundant buildnumber-maven-plugin
entry under pluginManagement that is incomplete (missing configuration like
doCheck, doUpdate, shortRevisionLength) while a full declaration already exists
under plugins; remove the plugin element from the pluginManagement section so
there is a single authoritative declaration of
org.codehaus.mojo:buildnumber-maven-plugin (the entry with execution goal
"create" in pluginManagement) and rely on the fully-configured plugin in the
<plugins> section to avoid configuration drift.

In `@src/main/resources/static/js/health.js`:
- Line 25: The polling interval for the status widget is too aggressive: replace
the current setInterval call that invokes loadHealth every 1_000 ms with a less
frequent interval (e.g., 5_000–10_000 ms) to reduce server load; update the
setInterval(loadHealth, 1_000) invocation in the file so it uses a 5000 (or
10000) millisecond interval instead while keeping the same loadHealth function
and semantics.
- Around line 19-21: The catch block that currently only sets
document.getElementById("health-status").textContent = "DOWN" leaves stale
values in other fields; update the error handler in health.js to also clear or
set to a neutral value (e.g., empty string or "N/A") the other DOM nodes such as
"local-time", "utc-time", "health-version", "health-commit" (or any similar IDs
used for time/version/commit) so that when the fetch fails all related fields
are reset instead of showing outdated data.

Comment thread pom.xml
Comment thread src/main/java/org/juv25d/plugin/HealthCheckPlugin.java Outdated
Comment thread src/main/java/org/juv25d/plugin/HealthCheckPlugin.java Outdated
Comment thread src/main/resources/static/css/styles.css
Comment thread src/main/resources/static/index.html Outdated
Comment thread src/main/resources/static/js/health.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 42-43: Rename the microsecond-valued field and references from
responseTimeMs to responseTimeUs: change the variable in HealthCheckPlugin from
responseTimeMs to responseTimeUs (computed as (System.nanoTime() -
req.creationTimeNanos()) / 1_000), update the JSON key emitted by
HealthCheckPlugin from "responseTimeMs" to "responseTimeUs", update the frontend
health.js to read data.responseTimeUs instead of data.responseTimeMs, and update
the assertion in HealthCheckPluginTest to expect "responseTimeUs".

---

Duplicate comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 27-34: The HealthCheckPlugin currently performs classpath I/O on
every /health request by loading build.properties inside the request handler;
move that loading into the class constructor or a static initializer and store
the parsed values in instance (or static) fields (e.g., buildVersion,
buildTimestamp) so the request method only reads the cached fields. Update the
code that currently uses Properties props in the request path to reference those
cached fields and remove the getResourceAsStream call from the per-request logic
in HealthCheckPlugin.
- Around line 51-74: The JSON construction in HealthCheckPlugin where jsonBody
is built interpolates version and commit raw (variables version and commit),
which breaks JSON if they contain " or \; replace this manual text block with a
proper JSON builder/serializer (e.g., construct an object and call
ObjectMapper.writeValueAsString or use JSONObject) to produce the payload, or at
minimum escape backslashes and quotes in version and commit before interpolation
(e.g., replace \ with \\ and " with \"); update the jsonBody creation code to
use the chosen approach so version and commit are safely serialized.

In `@src/main/resources/static/css/styles.css`:
- Around line 52-53: Remove the empty line immediately before the transition
declaration to satisfy the Stylelint declaration-empty-line-before rule; locate
the CSS rule containing the "transition: 150ms ease;" declaration and ensure
there's no blank line above it (or, if intentional, add a rule-specific
Stylelint disable comment for the property), then run Stylelint to confirm the
violation is resolved.
- Around line 73-74: Remove the empty line immediately before the CSS
declaration "position: absolute" to satisfy the stylelint rule
`declaration-empty-line-before`; locate the "position" declaration in the
stylesheet (the rule containing "position: absolute") and ensure there is no
blank line separating it from the previous declaration or selector, so the
declarations are contiguous and pass the linter.

In `@src/main/resources/static/js/health.js`:
- Around line 3-4: The code fetches "/health" and calls res.json()
unconditionally (res and data in this snippet); add a res.ok check after
fetching so non-2xx responses are not parsed as a valid health payload—if
!res.ok, either throw an Error or construct a fallback health object with status
"DOWN" (and optionally include res.status/text) so the catch path runs; update
the block around the const res = await fetch("/health") / const data = await
res.json() to perform this check and handle non-ok responses accordingly.

Comment thread src/main/java/org/juv25d/plugin/HealthCheckPlugin.java Outdated
…esilience; update resource filtering and enhance accessibility for health status UI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main/java/org/juv25d/plugin/HealthCheckPlugin.java (2)

34-34: Replace System.out.println with a proper logger.

Plain stdout output is not structured, not level-controlled, and easily lost in production.

♻️ Proposed refactor
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 public class HealthCheckPlugin implements Plugin {
 
+    private static final Logger LOGGER =
+        Logger.getLogger(HealthCheckPlugin.class.getName());
     private static final String SERVER_NAME = "juv25d-webserver";
 
     ...
 
-            System.out.println("Error loading build.properties: " + e.getMessage());
+            LOGGER.log(Level.WARNING, "Error loading build.properties", e);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` at line 34, Replace
the plain System.out.println in HealthCheckPlugin with a proper logger: add a
class-level logger (e.g., org.slf4j.Logger or java.util.logging.Logger) in
HealthCheckPlugin and change the System.out.println("Error loading
build.properties: " + e.getMessage()) to a logger.error (or logger.log with
SEVERE) call that includes both a descriptive message and the exception (pass
the exception object for stacktrace); also add the necessary import and
initialize the logger (e.g., getLogger for HealthCheckPlugin).

60-65: Capture a single timestamp, then format it in both zones.

Two back-to-back ZonedDateTime.now() calls can return different instants if the clock ticks between them.

♻️ Proposed refactor
-        String localTime = ZonedDateTime
-            .now(ZoneId.systemDefault())
-            .format(TIME_FORMAT);
-        String utcTime = ZonedDateTime
-            .now(ZoneId.of("UTC"))
-            .format(TIME_FORMAT);
+        ZonedDateTime nowLocal = ZonedDateTime.now(ZoneId.systemDefault());
+        String localTime = nowLocal.format(TIME_FORMAT);
+        String utcTime = nowLocal.withZoneSameInstant(ZoneId.of("UTC")).format(TIME_FORMAT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 60 - 65,
In HealthCheckPlugin, avoid calling ZonedDateTime.now() twice; capture a single
instant (e.g., Instant.now() or a single ZonedDateTime variable) and then derive
both local and UTC formatted strings from that same instant using TIME_FORMAT so
localTime and utcTime reflect the exact same instant; update the code that sets
localTime and utcTime to use that single captured timestamp and then
convert/format it for ZoneId.systemDefault() and ZoneId.of("UTC").
src/main/resources/static/js/health.js (1)

22-24: Catch block leaves other fields showing stale data.

When loadHealth() fails, only health-status is updated to "DOWN" while the previous values for local-time, utc-time, health-version, health-commit, health-response, and health-memory remain visible — mixing a failure state with stale success data.

♻️ Proposed refactor
     } catch (e) {
         document.getElementById("health-status").textContent = "DOWN";
+        ["local-time", "utc-time", "health-version",
+         "health-commit", "health-response", "health-memory"]
+            .forEach(id => {
+                const el = document.getElementById(id);
+                if (el) el.textContent = "—";
+            });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/health.js` around lines 22 - 24, When
loadHealth() catches an error, it only sets the "health-status" to "DOWN" and
leaves other DOM fields showing stale data; update the catch block in
loadHealth() to also reset/clear the other elements
(document.getElementById("local-time"), "utc-time", "health-version",
"health-commit", "health-response", "health-memory") to a safe fallback value
(e.g., "-" or empty string) so no stale success data remains visible when an
error occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/resources/static/js/health.js`:
- Around line 17-18: The code accesses data.memory.usedBytes and
data.memory.maxBytes directly (usedMb/maxMb) which throws if data.memory is
undefined; before computing usedMb/maxMb or updating the DOM, guard that data &&
data.memory exist and only compute usedMb/maxMb when present (otherwise set safe
fallback values like 'N/A' or skip updating those DOM fields), and ensure DOM
updates for memory happen after this validation so the page won't be left in a
partially-updated state if memory is missing; update the logic around the
usedMb/maxMb calculations in health.js to check data.memory first and handle the
absent-memory case gracefully.

---

Duplicate comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 58-59: Rename the incorrectly named responseTimeMs to
responseTimeUs in HealthCheckPlugin (change the variable responseTimeMs computed
with (System.nanoTime() - req.creationTimeNanos()) / 1_000 to responseTimeUs)
and update the JSON payload key (replace "responseTimeMs" with "responseTimeUs")
and all references; then update the frontend in health.js to use
data.responseTimeUs instead of data.responseTimeMs (and any UI labels/fields
that reference the old name).

---

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Line 34: Replace the plain System.out.println in HealthCheckPlugin with a
proper logger: add a class-level logger (e.g., org.slf4j.Logger or
java.util.logging.Logger) in HealthCheckPlugin and change the
System.out.println("Error loading build.properties: " + e.getMessage()) to a
logger.error (or logger.log with SEVERE) call that includes both a descriptive
message and the exception (pass the exception object for stacktrace); also add
the necessary import and initialize the logger (e.g., getLogger for
HealthCheckPlugin).
- Around line 60-65: In HealthCheckPlugin, avoid calling ZonedDateTime.now()
twice; capture a single instant (e.g., Instant.now() or a single ZonedDateTime
variable) and then derive both local and UTC formatted strings from that same
instant using TIME_FORMAT so localTime and utcTime reflect the exact same
instant; update the code that sets localTime and utcTime to use that single
captured timestamp and then convert/format it for ZoneId.systemDefault() and
ZoneId.of("UTC").

In `@src/main/resources/static/js/health.js`:
- Around line 22-24: When loadHealth() catches an error, it only sets the
"health-status" to "DOWN" and leaves other DOM fields showing stale data; update
the catch block in loadHealth() to also reset/clear the other elements
(document.getElementById("local-time"), "utc-time", "health-version",
"health-commit", "health-response", "health-memory") to a safe fallback value
(e.g., "-" or empty string) so no stale success data remains visible when an
error occurs.

Comment thread src/main/resources/static/js/health.js Outdated
@kappsegla

kappsegla commented Feb 21, 2026

Copy link
Copy Markdown

@bamsemats I wonder if the endpoint for receiving information about the server could be called something like /metrics instead and we can use /health to only report a simple OK or if we want it in json format {"status":"OK"} or {"status":"UP"}

Would like a very simple health endpoint to call when deploying so we can have automatic checks for if the server is down and needs to be restarted.

kristinaxm
kristinaxm previously approved these changes Feb 22, 2026
@kristinaxm
kristinaxm self-requested a review February 22, 2026 16:36
@bamsemats bamsemats changed the title Feature/health check plugin Feature/health check plugin + Metric Plugin Feb 22, 2026
@bamsemats

Copy link
Copy Markdown
Author

@bamsemats I wonder if the endpoint for receiving information about the server could be called something like /metrics instead and we can use /health to only report a simple OK or if we want it in json format {"status":"OK"} or {"status":"UP"}

Would like a very simple health endpoint to call when deploying so we can have automatic checks for if the server is down and needs to be restarted.

@bamsemats I wonder if the endpoint for receiving information about the server could be called something like /metrics instead and we can use /health to only report a simple OK or if we want it in json format {"status":"OK"} or {"status":"UP"}

Would like a very simple health endpoint to call when deploying so we can have automatic checks for if the server is down and needs to be restarted.

We will adjust this and put only the Status: UP into the HealthCheckPlugin, whilst implementing a new plugin called MetricPlugin with the rest of the stuff.

How should we think regarding the automatic checks for it the server is down?

…related routes, tests, and UI for enhanced monitoring capabilities.
…related routes, tests, and UI for enhanced monitoring capabilities.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/test/java/org/juv25d/plugin/MetricPluginTest.java (1)

18-18: Test uses /health path but MetricPlugin serves /metric.

The HttpRequest is constructed with path /health, but MetricPlugin is registered at /metric in App.java. While the plugin ignores the path in its handle() method, using the correct path improves test clarity and documentation.

♻️ Fix the path to match the actual endpoint
-        HttpRequest req = new HttpRequest("GET", "/health", null, "HTTP/1.1", Map.of(), new byte[0], "HEALTH");
+        HttpRequest req = new HttpRequest("GET", "/metric", null, "HTTP/1.1", Map.of(), new byte[0], "METRIC");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/plugin/MetricPluginTest.java` at line 18, Update the
HttpRequest created in the MetricPluginTest by changing the request path from
"/health" to "/metric" so the test request matches the registered endpoint for
MetricPlugin; locate the HttpRequest instantiation (the new HttpRequest("GET",
"/health", ...)) in MetricPluginTest and replace the path argument with
"/metric" to improve clarity and correctness relative to MetricPlugin and the
App registration.
src/main/java/org/juv25d/plugin/MetricPlugin.java (2)

29-31: Consider using the project's logger instead of System.out.

The codebase uses ServerLogging.getLogger() for logging (as seen in App.java). Using System.out.println bypasses the logging framework, making it harder to manage log levels and output destinations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 29 - 31,
Replace the System.out.println call in MetricPlugin's IOException catch (the
block handling loading of build.properties) with the project's logger: obtain
the logger via ServerLogging.getLogger() and log the error (e.g., logger.error
or logger.warn) including the exception or its message; update imports if needed
and ensure the log message gives the same context ("Error loading
build.properties") while passing the exception for full stacktrace.

41-46: Incomplete JSON escaping may produce invalid output.

The escapeJson method only escapes \ and ", but JSON requires escaping control characters (newlines, tabs, etc.). If build.properties values contain such characters, the output will be malformed JSON.

Consider handling at minimum: \n, \r, \t, and other control characters (U+0000–U+001F).

♻️ More robust escapeJson implementation
 private String escapeJson(String value) {
     if (value == null) {
         return "";
     }
-    return value.replace("\\", "\\\\").replace("\"", "\\\"");
+    StringBuilder sb = new StringBuilder();
+    for (char c : value.toCharArray()) {
+        switch (c) {
+            case '\\' -> sb.append("\\\\");
+            case '"' -> sb.append("\\\"");
+            case '\n' -> sb.append("\\n");
+            case '\r' -> sb.append("\\r");
+            case '\t' -> sb.append("\\t");
+            default -> {
+                if (c < 0x20) {
+                    sb.append(String.format("\\u%04x", (int) c));
+                } else {
+                    sb.append(c);
+                }
+            }
+        }
+    }
+    return sb.toString();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 41 - 46, The
escapeJson method in MetricPlugin only handles backslashes and double quotes,
which can produce invalid JSON when values contain control characters; update
the escapeJson(String value) implementation to iterate characters and replace
backspace (\b), formfeed (\f), newline (\n), carriage return (\r), tab (\t),
backslash and quote with their JSON escapes and convert any control characters
U+0000–U+001F to "\\uXXXX" escapes (or alternatively delegate to a JSON library
like Jackson's ObjectMapper.writeValueAsString to produce a safe JSON string);
locate the escapeJson method in MetricPlugin and implement the
character-by-character escaping or call a proven library function to ensure all
required escapes are covered.
src/main/resources/static/js/metric.js (2)

26-28: Consider clearing stale data on error.

When the fetch fails, only health-status is updated to "DOWN" while other fields retain their previous values. This could confuse users who see an old timestamp paired with a DOWN status.

♻️ Clear fields on error
     } catch (e) {
         document.getElementById("health-status").textContent = "DOWN";
+        document.getElementById("local-time").textContent = "—";
+        document.getElementById("utc-time").textContent = "—";
+        document.getElementById("health-version").textContent = "—";
+        document.getElementById("health-commit").textContent = "—";
+        document.getElementById("health-response").textContent = "—";
+        document.getElementById("health-memory").textContent = "—";
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/metric.js` around lines 26 - 28, In the catch
block in metric.js where you set
document.getElementById("health-status").textContent = "DOWN", also clear other
UI fields to avoid showing stale data: locate the catch block and set
document.getElementById("version").textContent and
document.getElementById("last-checked").textContent (and any metric elements
rendered by this file) to a neutral placeholder like "-" or "N/A", and remove or
update any status-related CSS classes on the "health-status" element so the UI
reflects the error state consistently.

13-14: Unit label "µs" contradicts field name responseTimeMs.

The display uses "µs" (microseconds), which actually matches what the backend calculates (nanoseconds / 1000), but contradicts the JSON field name responseTimeMs. Once the backend naming is corrected (see MetricPlugin.java review), update this accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/metric.js` around lines 13 - 14, The UI
currently appends " µs" while reading data.responseTimeMs; change the client to
use the corrected backend field name and matching unit — replace the assignment
that sets document.getElementById("health-response").textContent to use
data.responseTimeUs + " µs" (or if the backend instead becomes milliseconds, use
data.responseTimeMs + " ms"); ensure the symbol referenced is
document.getElementById("health-response") and the response field is
data.responseTimeUs (or data.responseTimeMs) so the label matches the backend
rename.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/juv25d/App.java`:
- Around line 54-55: HealthCheckPlugin contains an invalid JSON response with a
trailing comma; open the HealthCheckPlugin class
(org.juv25d.plugin.HealthCheckPlugin) and fix the JSON literal returned (in the
method that builds/returns the health JSON) by removing the extra comma so it
becomes {"status":"UP"}; ensure any string construction or serializer produces
valid JSON without dangling commas.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java`:
- Around line 54-55: The variable responseTimeMs is computed as
(System.nanoTime() - req.creationTimeNanos()) / 1_000 which yields microseconds,
so change the code to consistently use microseconds: rename the variable
responseTimeMs to responseTimeUs, update the JSON field name from
"responseTimeMs" to "responseTimeUs", and update any formatting/serialization
call that references responseTimeMs (e.g., the format argument used when
building the JSON) so all references (variable, JSON key, and any format/field
name passed to the serializer) use responseTimeUs; alternatively, if you really
want milliseconds, divide by 1_000_000 instead—pick one approach and make the
variable name, division, and JSON field consistent (preferred: rename to
responseTimeUs to match the frontend's µs display).

---

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/MetricPlugin.java`:
- Around line 29-31: Replace the System.out.println call in MetricPlugin's
IOException catch (the block handling loading of build.properties) with the
project's logger: obtain the logger via ServerLogging.getLogger() and log the
error (e.g., logger.error or logger.warn) including the exception or its
message; update imports if needed and ensure the log message gives the same
context ("Error loading build.properties") while passing the exception for full
stacktrace.
- Around line 41-46: The escapeJson method in MetricPlugin only handles
backslashes and double quotes, which can produce invalid JSON when values
contain control characters; update the escapeJson(String value) implementation
to iterate characters and replace backspace (\b), formfeed (\f), newline (\n),
carriage return (\r), tab (\t), backslash and quote with their JSON escapes and
convert any control characters U+0000–U+001F to "\\uXXXX" escapes (or
alternatively delegate to a JSON library like Jackson's
ObjectMapper.writeValueAsString to produce a safe JSON string); locate the
escapeJson method in MetricPlugin and implement the character-by-character
escaping or call a proven library function to ensure all required escapes are
covered.

In `@src/main/resources/static/js/metric.js`:
- Around line 26-28: In the catch block in metric.js where you set
document.getElementById("health-status").textContent = "DOWN", also clear other
UI fields to avoid showing stale data: locate the catch block and set
document.getElementById("version").textContent and
document.getElementById("last-checked").textContent (and any metric elements
rendered by this file) to a neutral placeholder like "-" or "N/A", and remove or
update any status-related CSS classes on the "health-status" element so the UI
reflects the error state consistently.
- Around line 13-14: The UI currently appends " µs" while reading
data.responseTimeMs; change the client to use the corrected backend field name
and matching unit — replace the assignment that sets
document.getElementById("health-response").textContent to use
data.responseTimeUs + " µs" (or if the backend instead becomes milliseconds, use
data.responseTimeMs + " ms"); ensure the symbol referenced is
document.getElementById("health-response") and the response field is
data.responseTimeUs (or data.responseTimeMs) so the label matches the backend
rename.

In `@src/test/java/org/juv25d/plugin/MetricPluginTest.java`:
- Line 18: Update the HttpRequest created in the MetricPluginTest by changing
the request path from "/health" to "/metric" so the test request matches the
registered endpoint for MetricPlugin; locate the HttpRequest instantiation (the
new HttpRequest("GET", "/health", ...)) in MetricPluginTest and replace the path
argument with "/metric" to improve clarity and correctness relative to
MetricPlugin and the App registration.

Comment thread src/main/java/org/juv25d/App.java
Comment thread src/main/java/org/juv25d/plugin/MetricPlugin.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/main/java/org/juv25d/plugin/HealthCheckPlugin.java (1)

20-24: Nit: inconsistent closing brace indentation in the JSON text block.

The closing } is indented with extra spaces compared to the opening {, producing slightly misaligned JSON output. Cosmetic only.

♻️ Suggested tweak
         String jsonBody = """
             {
               "status": "UP"
-              }
+            }
             """;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java` around lines 20 - 24,
The JSON text block assigned to jsonBody in HealthCheckPlugin.java has a
misaligned closing brace; fix the multi-line string so the closing `}` lines up
with the opening `{` (i.e., remove the extra leading spaces before the closing
brace) to produce aligned JSON output from the jsonBody variable.
src/main/java/org/juv25d/plugin/MetricPlugin.java (1)

56-61: localTime / utcTime are interpolated without JSON escaping.

These formatted time strings are injected directly into the JSON template via %s. While standard time zone abbreviations are safe, running on a JVM with an unusual locale/zone configuration could theoretically produce characters that break the JSON. Consider passing them through escapeJson() for consistency with the other string fields.

♻️ Suggested tweak
-        String localTime = ZonedDateTime
+        String localTime = escapeJson(ZonedDateTime
             .now(ZoneId.systemDefault())
-            .format(TIME_FORMAT);
-        String utcTime = ZonedDateTime
+            .format(TIME_FORMAT));
+        String utcTime = escapeJson(ZonedDateTime
             .now(ZoneId.of("UTC"))
-            .format(TIME_FORMAT);
+            .format(TIME_FORMAT));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 56 - 61,
Escape the formatted time strings before injecting into the JSON template:
replace direct uses of localTime and utcTime in MetricPlugin (where they are
built with ZonedDateTime.now(...).format(TIME_FORMAT)) with escaped values by
passing them through escapeJson(), e.g. compute escapedLocalTime =
escapeJson(localTime) and escapedUtcTime = escapeJson(utcTime) and use those
escaped variables in the JSON interpolation to match how other string fields are
handled.
src/main/resources/static/js/metric.js (1)

16-21: Memory field-level null check is shallow.

data.memory being truthy doesn't guarantee usedBytes / maxBytes are present. If the backend returns "memory": {}, the division produces NaN. Consider guarding individual fields as well.

♻️ Suggested improvement
-        const usedMb = data.memory
-            ? (data.memory.usedBytes / 1024 / 1024).toFixed(1)
+        const usedMb = data.memory?.usedBytes != null
+            ? (data.memory.usedBytes / 1024 / 1024).toFixed(1)
             : "N/A";
-        const maxMb = data.memory
-            ? (data.memory.maxBytes / 1024 / 1024).toFixed(1)
+        const maxMb = data.memory?.maxBytes != null
+            ? (data.memory.maxBytes / 1024 / 1024).toFixed(1)
             : "N/A";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/static/js/metric.js` around lines 16 - 21, The shallow
null check on data.memory can still yield NaN when usedBytes or maxBytes are
missing; update the usedMb and maxMb assignments to individually guard and
validate data.memory.usedBytes and data.memory.maxBytes (e.g., check existence
and Number.isFinite or typeof === "number") before performing the division and
toFixed, and fall back to "N/A" if the field is absent/invalid—apply this to the
expressions that compute usedMb and maxMb referencing data.memory.usedBytes and
data.memory.maxBytes.
src/test/java/org/juv25d/plugin/MetricPluginTest.java (1)

18-18: Test request path is /health but MetricPlugin serves /metric.

Since the plugin is invoked directly (not via the router), this doesn't affect correctness, but updating the path to "/metric" would make the test self-documenting.

♻️ Suggested tweak
-        HttpRequest req = new HttpRequest("GET", "/health", null, "HTTP/1.1", Map.of(), new byte[0], "HEALTH");
+        HttpRequest req = new HttpRequest("GET", "/metric", null, "HTTP/1.1", Map.of(), new byte[0], "METRIC");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/juv25d/plugin/MetricPluginTest.java` at line 18, Update the
test's HttpRequest to use the plugin's actual path so it's self-documenting:
change the request path argument passed to the HttpRequest constructor in
MetricPluginTest from "/health" to "/metric" (the HttpRequest instantiation that
currently reads HttpRequest req = new HttpRequest("GET", "/health", ...)); no
other behavior changes required since MetricPlugin is invoked directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/resources/static/js/metric.js`:
- Around line 1-29: The loadHealth function's success path never sets the
health-status element back to "UP", so once the catch sets it to "DOWN" it
remains that way; update loadHealth to explicitly set
document.getElementById("health-status").textContent = "UP" (or equivalent
healthy indicator) in the successful branch after data is applied (e.g., near
the end of the try block after updating health-memory) and ensure the catch
still sets it to "DOWN" to restore correct status transitions.

In `@src/test/java/org/juv25d/plugin/MetricPluginTest.java`:
- Line 33: The test assertion in MetricPluginTest is still checking for the old
JSON field name "responseTimeMs" while MetricPlugin now emits "responseTimeUs";
update the assertion in the test (the assertTrue call that checks
body.contains(...)) to look for "\"responseTimeUs\"" and adjust the assertion
message accordingly so it verifies the new field emitted by MetricPlugin.

---

Duplicate comments:
In `@src/main/java/org/juv25d/App.java`:
- Around line 54-55: The route registration for the specific endpoints is
correct; ensure you keep the two calls router.registerPlugin("/metric", new
MetricPlugin()) and router.registerPlugin("/health", new HealthCheckPlugin())
placed before any wildcard registrations (e.g., "/", "/*") so they take
precedence, and verify MetricPlugin and HealthCheckPlugin constructors and
exported types match the router.registerPlugin signature used elsewhere.

---

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/HealthCheckPlugin.java`:
- Around line 20-24: The JSON text block assigned to jsonBody in
HealthCheckPlugin.java has a misaligned closing brace; fix the multi-line string
so the closing `}` lines up with the opening `{` (i.e., remove the extra leading
spaces before the closing brace) to produce aligned JSON output from the
jsonBody variable.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java`:
- Around line 56-61: Escape the formatted time strings before injecting into the
JSON template: replace direct uses of localTime and utcTime in MetricPlugin
(where they are built with ZonedDateTime.now(...).format(TIME_FORMAT)) with
escaped values by passing them through escapeJson(), e.g. compute
escapedLocalTime = escapeJson(localTime) and escapedUtcTime =
escapeJson(utcTime) and use those escaped variables in the JSON interpolation to
match how other string fields are handled.

In `@src/main/resources/static/js/metric.js`:
- Around line 16-21: The shallow null check on data.memory can still yield NaN
when usedBytes or maxBytes are missing; update the usedMb and maxMb assignments
to individually guard and validate data.memory.usedBytes and
data.memory.maxBytes (e.g., check existence and Number.isFinite or typeof ===
"number") before performing the division and toFixed, and fall back to "N/A" if
the field is absent/invalid—apply this to the expressions that compute usedMb
and maxMb referencing data.memory.usedBytes and data.memory.maxBytes.

In `@src/test/java/org/juv25d/plugin/MetricPluginTest.java`:
- Line 18: Update the test's HttpRequest to use the plugin's actual path so it's
self-documenting: change the request path argument passed to the HttpRequest
constructor in MetricPluginTest from "/health" to "/metric" (the HttpRequest
instantiation that currently reads HttpRequest req = new HttpRequest("GET",
"/health", ...)); no other behavior changes required since MetricPlugin is
invoked directly.

Comment thread src/main/resources/static/js/metric.js
Comment thread src/test/java/org/juv25d/plugin/MetricPluginTest.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/main/java/org/juv25d/plugin/MetricPlugin.java (3)

45-50: escapeJson does not escape control characters — could produce invalid JSON.

The method only handles \ and ". If a build.properties value contains a literal newline, tab, carriage return, or other control character (U+0000–U+001F), the JSON output will be syntactically invalid. A minimal fix:

♻️ Proposed refactor
 private String escapeJson(String value) {
     if (value == null) {
         return "";
     }
-    return value.replace("\\", "\\\\").replace("\"", "\\\"");
+    return value.replace("\\", "\\\\")
+                .replace("\"", "\\\"")
+                .replace("\n", "\\n")
+                .replace("\r", "\\r")
+                .replace("\t", "\\t")
+                .replace("\b", "\\b")
+                .replace("\f", "\\f");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 45 - 50, The
escapeJson method in MetricPlugin only handles backslash and double-quote and
must also escape JSON control characters (U+0000–U+001F) to produce valid JSON;
update the escapeJson(String value) implementation to iterate characters and
replace control chars with their JSON escapes (\b, \f, \n, \r, \t) and use
\\uXXXX escapes for any other codepoints <= 0x1F, while still escaping backslash
and double-quote, so that any build.properties value containing newlines, tabs,
nulls, etc. is safely serialized.

67-89: localTime and utcTime bypass escapeJson; consider a JSON builder for robustness.

There are two related issues:

  1. localTime and utcTime are injected via %s without sanitization. While the DateTimeFormatter output is safe today, if the system's timezone abbreviation changes to contain \ or ", the JSON silently breaks.
  2. The hand-rolled String.format template is a fragile baseline — each new string field requires a manual decision to escape, and it's easy to miss (as happened here with the time fields).

The minimal fix is to pass all string fields through escapeJson:

♻️ Proposed minimal fix
         String jsonBody = String.format("""
             {
               "localTime": "%s",
               "utcTime": "%s",
               "server": "%s",
               "buildVersion": "%s",
               "gitCommit": "%s",
               "responseTimeUs": %d,
               "memory": {
                 "usedBytes": %d,
                 "maxBytes": %d
               }
             }
             """,
-            localTime,
-            utcTime,
-            SERVER_NAME,
+            escapeJson(localTime),
+            escapeJson(utcTime),
+            escapeJson(SERVER_NAME),
             version,
             commit,

As a longer-term improvement, consider replacing the manual template with a lightweight JSON construction approach (e.g., jakarta.json, org.json, or even a simple JsonObject builder from a library already in the project) to avoid the class of "forgot to escape" bugs entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 67 - 89, In
MetricPlugin where the JSON body is built with String.format, the time fields
localTime and utcTime (and any other string fields such as SERVER_NAME, version,
commit) are inserted without escaping; update the String.format invocation to
wrap every string argument with escapeJson (e.g., escapeJson(localTime),
escapeJson(utcTime), escapeJson(SERVER_NAME), escapeJson(version),
escapeJson(commit)) so all string fields are escaped before being placed into
the JSON template; for a longer-term fix consider replacing this hand-rolled
template in MetricPlugin with a proper JSON builder
(jakarta.json/org.json/JsonObject) to avoid future missed escapes.

60-65: Two separate ZonedDateTime.now() calls capture different instants.

localTime and utcTime are snapshotted from two distinct clock reads, so they can theoretically reflect different timestamps under load. Capture once and convert:

♻️ Proposed refactor
-        String localTime = ZonedDateTime
-            .now(ZoneId.systemDefault())
-            .format(TIME_FORMAT);
-        String utcTime = ZonedDateTime
-            .now(ZoneId.of("UTC"))
-            .format(TIME_FORMAT);
+        ZonedDateTime nowUtc = ZonedDateTime.now(ZoneId.of("UTC"));
+        String utcTime = nowUtc.format(TIME_FORMAT);
+        String localTime = nowUtc.withZoneSameInstant(ZoneId.systemDefault()).format(TIME_FORMAT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/juv25d/plugin/MetricPlugin.java` around lines 60 - 65, In
MetricPlugin replace the two separate ZonedDateTime.now(...) calls with a single
snapshot: get one ZonedDateTime (e.g., ZonedDateTime now =
ZonedDateTime.now(ZoneId.systemDefault());), format localTime from that snapshot
using TIME_FORMAT, and derive utcTime by converting the same instant with
now.withZoneSameInstant(ZoneId.of("UTC")).format(TIME_FORMAT) so both strings
reflect the exact same instant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/java/org/juv25d/plugin/MetricPlugin.java`:
- Around line 58-59: The variable naming/unit is already corrected: ensure the
computed value in MetricPlugin (variable responseTimeUs in the code that does
(System.nanoTime() - req.creationTimeNanos()) / 1_000) and the JSON field
"responseTimeUs" remain consistent; no code changes required—just keep
responseTimeUs as the microsecond value and the JSON key "responseTimeUs".

---

Nitpick comments:
In `@src/main/java/org/juv25d/plugin/MetricPlugin.java`:
- Around line 45-50: The escapeJson method in MetricPlugin only handles
backslash and double-quote and must also escape JSON control characters
(U+0000–U+001F) to produce valid JSON; update the escapeJson(String value)
implementation to iterate characters and replace control chars with their JSON
escapes (\b, \f, \n, \r, \t) and use \\uXXXX escapes for any other codepoints <=
0x1F, while still escaping backslash and double-quote, so that any
build.properties value containing newlines, tabs, nulls, etc. is safely
serialized.
- Around line 67-89: In MetricPlugin where the JSON body is built with
String.format, the time fields localTime and utcTime (and any other string
fields such as SERVER_NAME, version, commit) are inserted without escaping;
update the String.format invocation to wrap every string argument with
escapeJson (e.g., escapeJson(localTime), escapeJson(utcTime),
escapeJson(SERVER_NAME), escapeJson(version), escapeJson(commit)) so all string
fields are escaped before being placed into the JSON template; for a longer-term
fix consider replacing this hand-rolled template in MetricPlugin with a proper
JSON builder (jakarta.json/org.json/JsonObject) to avoid future missed escapes.
- Around line 60-65: In MetricPlugin replace the two separate
ZonedDateTime.now(...) calls with a single snapshot: get one ZonedDateTime
(e.g., ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());), format
localTime from that snapshot using TIME_FORMAT, and derive utcTime by converting
the same instant with
now.withZoneSameInstant(ZoneId.of("UTC")).format(TIME_FORMAT) so both strings
reflect the exact same instant.

kristinaxm
kristinaxm previously approved these changes Feb 22, 2026

@kristinaxm kristinaxm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work 👍

@fmazmz
fmazmz merged commit 8b2b644 into main Feb 22, 2026
2 checks passed
@fmazmz
fmazmz deleted the feature/health-check-plugin branch February 22, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement metric plugin Implement HealthCheckPlugin for Server Monitoring

5 participants