From 73016ae2fd59df949aaca62f4e5e3b2714859f76 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 5 Dec 2024 16:48:26 +0100 Subject: [PATCH 1/5] implement /actuator/custom endpoint for linked metrics endpoints --- .../config/CustomActuatorEndpoint.java | 42 ++++++++++++++++++ src/main/resources/application.yml | 2 +- .../CustomActuatorEndpointAcceptanceTest.java | 43 +++++++++++++++++++ src/test/resources/application.yml | 2 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java create mode 100644 src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java new file mode 100644 index 00000000..4c0a4f5b --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java @@ -0,0 +1,42 @@ +package net.hostsharing.hsadminng.config; + +import lombok.Getter; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.util.List; + +@Component +@Endpoint(id="custom") +public class CustomActuatorEndpoint { + + private final RestTemplate restTemplate = new RestTemplate(); + + @ReadOperation + public String getMetricsLinks() { + final String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString(); + final var metricsEndpoint = baseUrl + "/actuator/metrics"; + + final var response = restTemplate.getForObject(metricsEndpoint, ActuatorMetricsEndpointResource.class); + + if (response == null || response.getNames() == null) { + throw new IllegalStateException("no metrics available"); + } + return generateJsonLinksToMetricEndpoints(response, metricsEndpoint); + } + + private static String generateJsonLinksToMetricEndpoints(final ActuatorMetricsEndpointResource response, final String metricsEndpoint) { + final var links = response.getNames().stream() + .map(name -> "\"" + name + "\": \"" + metricsEndpoint + "/" + name + "\"") + .toList(); + return "{\n" + String.join(",\n", links) + "\n}"; + } + + @Getter + private static class ActuatorMetricsEndpointResource { + private List names; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 27020234..d747b9e2 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,7 +8,7 @@ management: endpoints: web: exposure: - include: info, health, metrics + include: info, health, metrics, custom observations: annotations: enabled: true diff --git a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java new file mode 100644 index 00000000..8dabfa6c --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java @@ -0,0 +1,43 @@ +package net.hostsharing.hsadminng.config; + +import io.restassured.RestAssured; +import net.hostsharing.hsadminng.HsadminNgApplication; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import net.hostsharing.hsadminng.test.DisableSecurityConfig; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalManagementPort; +import org.springframework.test.context.ActiveProfiles; + +import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { HsadminNgApplication.class, DisableSecurityConfig.class, JpaAttempt.class } +) +@ActiveProfiles("test") +class CustomActuatorEndpointAcceptanceTest { + + @LocalManagementPort + private Integer managementPort; + + @Test + void shouldListMetricLinks() { + RestAssured // @formatter:off + .given() + .port(managementPort) + .when() + .get("http://localhost/actuator/custom") + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/vnd.spring-boot.actuator.v3+json") + .body("", lenientlyEquals(""" + { + "application.ready.time": "http://localhost:%{managementPort}/actuator/metrics/application.ready.time", + "application.started.time": "http://localhost:%{managementPort}/actuator/metrics/application.started.time" + } + """.replace("%{managementPort}", managementPort.toString()))); + // @formatter:on + } + +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index a365daf3..871997c4 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -6,7 +6,7 @@ management: endpoints: web: exposure: - include: info, health, metrics + include: info, health, metrics, custom spring: sql: -- 2.39.5 From 31b488206538037dba0d85ecf6221e7c37d900b9 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 5 Dec 2024 16:51:20 +0100 Subject: [PATCH 2/5] rename to /actuator/metric-links --- .../hostsharing/hsadminng/config/CustomActuatorEndpoint.java | 2 +- src/main/resources/application.yml | 2 +- .../hsadminng/config/CustomActuatorEndpointAcceptanceTest.java | 2 +- src/test/resources/application.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java index 4c0a4f5b..802838a6 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java +++ b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java @@ -10,7 +10,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.util.List; @Component -@Endpoint(id="custom") +@Endpoint(id="metric-links") public class CustomActuatorEndpoint { private final RestTemplate restTemplate = new RestTemplate(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d747b9e2..abd50d19 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,7 +8,7 @@ management: endpoints: web: exposure: - include: info, health, metrics, custom + include: info, health, metrics, metric-links observations: annotations: enabled: true diff --git a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java index 8dabfa6c..9f3ae676 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java @@ -27,7 +27,7 @@ class CustomActuatorEndpointAcceptanceTest { .given() .port(managementPort) .when() - .get("http://localhost/actuator/custom") + .get("http://localhost/actuator/metric-links") .then().log().all().assertThat() .statusCode(200) .contentType("application/vnd.spring-boot.actuator.v3+json") diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 871997c4..f0df4e4b 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -6,7 +6,7 @@ management: endpoints: web: exposure: - include: info, health, metrics, custom + include: info, health, metrics, metric-links spring: sql: -- 2.39.5 From 2efc33826d0c79e4be04508df896179c2752e1fc Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 5 Dec 2024 17:01:08 +0100 Subject: [PATCH 3/5] formatting --- .../hsadminng/config/CustomActuatorEndpointAcceptanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java index 9f3ae676..78275d71 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java @@ -37,7 +37,7 @@ class CustomActuatorEndpointAcceptanceTest { "application.started.time": "http://localhost:%{managementPort}/actuator/metrics/application.started.time" } """.replace("%{managementPort}", managementPort.toString()))); - // @formatter:on + // @formatter:on } } -- 2.39.5 From c87b88ff4cab6ec68055b7ea79ddd47467e87b4b Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 08:53:00 +0100 Subject: [PATCH 4/5] fix ArchitectureTest --- .../java/net/hostsharing/hsadminng/arch/ArchitectureTest.java | 2 ++ .../hsadminng/config/CustomActuatorEndpointAcceptanceTest.java | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index 2628ad5d..2bf87f09 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -16,6 +16,7 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRbacEntity; import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; import net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.repository.Repository; import org.springframework.web.bind.annotation.RestController; @@ -120,6 +121,7 @@ public class ArchitectureTest { @SuppressWarnings("unused") public static final ArchRule configPackageRule = classes() .that().resideInAPackage("..config..") + .and().areNotAnnotatedWith(SpringBootTest.class) .should().onlyDependOnClassesThat() .resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG); diff --git a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java index 78275d71..1509831e 100644 --- a/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/config/CustomActuatorEndpointAcceptanceTest.java @@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.config; import io.restassured.RestAssured; import net.hostsharing.hsadminng.HsadminNgApplication; -import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.test.DisableSecurityConfig; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -13,7 +12,7 @@ import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = { HsadminNgApplication.class, DisableSecurityConfig.class, JpaAttempt.class } + classes = { HsadminNgApplication.class, DisableSecurityConfig.class } ) @ActiveProfiles("test") class CustomActuatorEndpointAcceptanceTest { -- 2.39.5 From 346b9a1bc2e7466f3d13f35265e9117374ba2100 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 09:07:44 +0100 Subject: [PATCH 5/5] add HOWTO+BLOG comments --- .../hostsharing/hsadminng/config/CustomActuatorEndpoint.java | 2 ++ src/main/resources/application.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java index 802838a6..942ddbb9 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java +++ b/src/main/java/net/hostsharing/hsadminng/config/CustomActuatorEndpoint.java @@ -11,6 +11,8 @@ import java.util.List; @Component @Endpoint(id="metric-links") +// BLOG: implement a custom Spring Actuator endpoint to view _clickable_ Spring Actuator (Micrometer) Metrics endpoints +// HOWTO: implement a custom Spring Actuator endpoint public class CustomActuatorEndpoint { private final RestTemplate restTemplate = new RestTemplate(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index abd50d19..f75ae429 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,7 @@ management: endpoints: web: exposure: + # HOWTO: view _clickable_ Spring Actuator (Micrometer) Metrics endpoints: http://localhost:8081/actuator/metric-links include: info, health, metrics, metric-links observations: annotations: -- 2.39.5