diff --git a/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestClientHealthIndicator.java b/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestClientHealthIndicator.java new file mode 100644 index 0000000..88c7a10 --- /dev/null +++ b/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestClientHealthIndicator.java @@ -0,0 +1,68 @@ +package org.wikitolearn.midtier.course.actuate.health; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +import io.micrometer.core.instrument.MeterRegistry; + +@Component +public class RestClientHealthIndicator implements HealthIndicator { + + private MeterRegistry registry; + + private final List notableStatusTags; + private final List ignoredUriTags; + + @Autowired + public RestClientHealthIndicator(MeterRegistry registry) { + this.registry = registry; + this.notableStatusTags = new ArrayList<>(); + this.notableStatusTags.add("500"); + this.notableStatusTags.add("503"); + this.notableStatusTags.add("CLIENT_ERROR"); + this.ignoredUriTags = new ArrayList<>(); + } + + @Override + public Health health() { + + if (registry.find("http.client.requests").meters().isEmpty()) { + return Health.up().build(); + } else { + double clientTotal = registry.get("http.client.requests").timer().count(); + + double errorCounter = this.calculateErrorCounter(); + double percentage = (errorCounter / clientTotal) * 100; + + if(percentage > 90 && clientTotal > 100) { + return Health.down().build(); + } + + return Health.up().build(); + } + } + + private double calculateErrorCounter() { + double notableValue = 0.0; + double ignoredValue = 0.0; + + for(String value : notableStatusTags) { + notableValue += registry.find("http.client.requests").tag("status", value).meters().isEmpty() + ? 0.0 + : registry.get("http.client.requests").tag("status", value).timer().count(); + } + + for(String value : ignoredUriTags) { + ignoredValue += registry.find("http.client.requests").tag("uri", value).meters().isEmpty() + ? 0.0 + : registry.get("http.client.requests").tag("uri", value).timer().count(); + } + + return notableValue - ignoredValue; + } +} \ No newline at end of file diff --git a/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestControllerHealthIndicator.java b/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestControllerHealthIndicator.java new file mode 100644 index 0000000..1cf7d8a --- /dev/null +++ b/src/main/java/org/wikitolearn/midtier/course/actuate/health/RestControllerHealthIndicator.java @@ -0,0 +1,68 @@ +package org.wikitolearn.midtier.course.actuate.health; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +import io.micrometer.core.instrument.MeterRegistry; + +@Component +public class RestControllerHealthIndicator implements HealthIndicator { + + private MeterRegistry registry; + private final List notableStatusTags; + private final List ignoredUriTags; + + @Autowired + public RestControllerHealthIndicator(MeterRegistry registry) { + this.registry = registry; + this.notableStatusTags = new ArrayList<>(); + this.notableStatusTags.add("500"); + this.notableStatusTags.add("503"); + this.ignoredUriTags = new ArrayList<>(); + this.ignoredUriTags.add("/_meta/status"); + this.ignoredUriTags.add("/**/favicon.ico"); + } + + @Override + public Health health() { + + if (registry.find("http.server.requests").meters().isEmpty()) { + return Health.up().build(); + } else { + + double controllerTotal = registry.get("http.server.requests").timer().count(); + double errorCounter = this.calculateErrorCounter(); + double percentage = (errorCounter / controllerTotal) * 100; + + if(percentage > 90 && controllerTotal > 100) { + return Health.down().build(); + } + + return Health.up().build(); + } + } + + private double calculateErrorCounter() { + double notableValue = 0.0; + double ignoredValue = 0.0; + + for(String value : notableStatusTags) { + notableValue += registry.find("http.server.requests").tag("status", value).meters().isEmpty() + ? 0.0 + : registry.get("http.server.requests").tag("status", value).timer().count(); + } + + for(String value : ignoredUriTags) { + ignoredValue += registry.find("http.server.requests").tag("uri", value).meters().isEmpty() + ? 0.0 + : registry.get("http.server.requests").tag("uri", value).timer().count(); + } + + return notableValue - ignoredValue; + } +} \ No newline at end of file diff --git a/src/main/java/org/wikitolearn/midtier/course/actuate/metrics/web/client/CustomRestTemplateExchangeTagsProvider.java b/src/main/java/org/wikitolearn/midtier/course/actuate/metrics/web/client/CustomRestTemplateExchangeTagsProvider.java new file mode 100644 index 0000000..fec9041 --- /dev/null +++ b/src/main/java/org/wikitolearn/midtier/course/actuate/metrics/web/client/CustomRestTemplateExchangeTagsProvider.java @@ -0,0 +1,19 @@ +package org.wikitolearn.midtier.course.actuate.metrics.web.client; + +import java.util.Arrays; + +import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTags; +import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +import io.micrometer.core.instrument.Tag; + +public class CustomRestTemplateExchangeTagsProvider implements RestTemplateExchangeTagsProvider { + + @Override + public Iterable getTags(String urlTemplate, HttpRequest request, ClientHttpResponse response) { + return Arrays.asList(RestTemplateExchangeTags.method(request), RestTemplateExchangeTags.status(response), + RestTemplateExchangeTags.clientName(request)); + } +} diff --git a/src/main/java/org/wikitolearn/midtier/course/config/MetricsConfiguration.java b/src/main/java/org/wikitolearn/midtier/course/config/MetricsConfiguration.java new file mode 100644 index 0000000..d23484c --- /dev/null +++ b/src/main/java/org/wikitolearn/midtier/course/config/MetricsConfiguration.java @@ -0,0 +1,14 @@ +package org.wikitolearn.midtier.course.config; + +import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.wikitolearn.midtier.course.actuate.metrics.web.client.CustomRestTemplateExchangeTagsProvider; + +@Configuration +public class MetricsConfiguration { + @Bean + public RestTemplateExchangeTagsProvider restTemplateExchangeTagsProvider() { + return new CustomRestTemplateExchangeTagsProvider(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f8220b8..043a39d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,49 +1,62 @@ # Spring properties spring: application: name: CourseMidTier output: ansi: enabled: DETECT +# Management properties +management: + endpoints: + web: + base-path: /_meta + path-mapping: + health: status + exposure: + include: health,info,metrics + health: + defaults: + enabled: false + # Security properties security: oauth2: resource: user-info-uri: ${KEYCLOAK_URI}/auth/realms/${KEYCLOAK_AUTH_REALM}/protocol/openid-connect/userinfo token-info-uri: ${KEYCLOAK_URI}/auth/realms/${KEYCLOAK_AUTH_REALM}/protocol/openid-connect/token/introspect prefer-token-info: false jwk: key-set-uri: ${KEYCLOAK_URI}/auth/realms/${KEYCLOAK_AUTH_REALM}/protocol/openid-connect/certs # Springfox properties springfox: documentation: swagger: v2: path: /api-docs # Logging properties logging: pattern: file: '%d{"yyyy-MM-dd HH:mm:ss,SSS"} %-5level [%c] \(%thread\) : %msg%n' console: '%d{"yyyy-MM-dd HH:mm:ss,SSS"} %-5level [%c] \(%thread\) : %msg%n' # Server properties server: port: ${SERVICE_PORT} ssl: enabled: ${SSL_ENABLED} key-store: file:./app-keystore.jks key-store-password: changeme key-password: changeme trust-store: file:./app-truststore.jks trust-store-password: changeme client-auth: ${MTLS_STATUS} # Application properties application: clients: courses-backend: ${COURSES_BACKEND_URI}/v1 chapters-backend: ${CHAPTERS_BACKEND_URI}/v1 - pages-backend: ${PAGES_BACKEND_URI}/v1 \ No newline at end of file + pages-backend: ${PAGES_BACKEND_URI}/v1