From 70788002d003a6d09706848f8f74ef7f8aca3767 Mon Sep 17 00:00:00 2001 From: 98001yash Date: Sat, 29 Aug 2026 01:21:41 +0530 Subject: [PATCH] Log health contributor statuses in Eureka health checks Signed-off-by: 98001yash --- .../eureka/EurekaHealthCheckHandler.java | 41 +++++++----- .../eureka/EurekaHealthCheckHandlerTests.java | 65 +++++++++++++++++++ 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java index ff89ef7f9..9b3dec21f 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java @@ -19,11 +19,12 @@ package org.springframework.cloud.netflix.eureka; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Set; import com.netflix.appinfo.HealthCheckHandler; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -68,6 +69,8 @@ import org.springframework.util.Assert; public class EurekaHealthCheckHandler implements HealthCheckHandler, ApplicationContextAware, InitializingBean, Ordered, Lifecycle { + private static final Log log = LogFactory.getLog(EurekaHealthCheckHandler.class); + private static final Map STATUS_MAPPING = new HashMap<>() { { put(Status.UNKNOWN, InstanceStatus.UNKNOWN); @@ -127,7 +130,12 @@ public class EurekaHealthCheckHandler @Override public InstanceStatus getStatus(InstanceStatus instanceStatus) { if (running) { - return getHealthStatus(); + Map statuses = new HashMap<>(); + InstanceStatus status = getHealthStatus(statuses); + if (status != instanceStatus) { + log.info("Eureka health status changed to " + status + " with health contributors " + statuses); + } + return status; } else { // Return nothing if the context is not running, so the status held by the @@ -137,43 +145,42 @@ public class EurekaHealthCheckHandler } } - protected InstanceStatus getHealthStatus() { - Status status = getStatus(statusAggregator); + protected InstanceStatus getHealthStatus(Map statuses) { + Status status = getStatus(statusAggregator, statuses); return mapToInstanceStatus(status); } - protected Status getStatus(StatusAggregator statusAggregator) { - Set statusSet = new HashSet<>(); - for (HealthContributor contributor : healthContributors.values()) { - processContributor(statusSet, contributor); + protected Status getStatus(StatusAggregator statusAggregator, Map statuses) { + for (Map.Entry entry : healthContributors.entrySet()) { + processContributor(statuses, entry.getKey(), entry.getValue()); } - for (ReactiveHealthContributor contributor : reactiveHealthContributors.values()) { - processContributor(statusSet, contributor); + for (Map.Entry entry : reactiveHealthContributors.entrySet()) { + processContributor(statuses, entry.getKey(), entry.getValue()); } - return statusAggregator.getAggregateStatus(statusSet); + return statusAggregator.getAggregateStatus(new HashSet<>(statuses.values())); } - private void processContributor(Set statusSet, HealthContributor contributor) { + private void processContributor(Map statuses, String name, HealthContributor contributor) { if (contributor instanceof CompositeHealthContributor) { for (HealthContributors.Entry contrib : (CompositeHealthContributor) contributor) { - processContributor(statusSet, contrib.contributor()); + processContributor(statuses, contrib.name(), contrib.contributor()); } } else if (contributor instanceof HealthIndicator) { - statusSet.add(((HealthIndicator) contributor).health().getStatus()); + statuses.put(name, ((HealthIndicator) contributor).health().getStatus()); } } - private void processContributor(Set statusSet, ReactiveHealthContributor contributor) { + private void processContributor(Map statuses, String name, ReactiveHealthContributor contributor) { if (contributor instanceof CompositeReactiveHealthContributor) { for (ReactiveHealthContributors.Entry contrib : (CompositeReactiveHealthContributor) contributor) { - processContributor(statusSet, contrib.contributor()); + processContributor(statuses, contrib.name(), contrib.contributor()); } } else if (contributor instanceof ReactiveHealthIndicator) { Health health = ((ReactiveHealthIndicator) contributor).health().block(); if (health != null) { - statusSet.add(health.getStatus()); + statuses.put(name, health.getStatus()); } } } diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java index f4eacdee1..7ad240230 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java @@ -30,17 +30,20 @@ import org.springframework.boot.health.actuate.endpoint.SimpleStatusAggregator; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator; import org.springframework.boot.health.contributor.CompositeHealthContributor; +import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor; import org.springframework.boot.health.contributor.Health; import org.springframework.boot.health.contributor.HealthContributor; import org.springframework.boot.health.contributor.HealthContributors; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.health.contributor.ReactiveHealthIndicator; +import org.springframework.boot.health.contributor.Status; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator; import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthContributor; import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -161,6 +164,30 @@ class EurekaHealthCheckHandlerTests { assertThat(status).isEqualTo(InstanceStatus.DOWN); } + @Test + void testContributorStatusesWithBlockingIndicators() { + initialize(NamedUpHealthConfiguration.class, NamedDownHealthConfiguration.class); + + Map statuses = new HashMap<>(); + Status status = healthCheckHandler.getStatus(new SimpleStatusAggregator(), statuses); + + assertThat(status).isEqualTo(Status.DOWN); + assertThat(statuses).containsEntry("upHealthIndicator", Status.UP); + assertThat(statuses).containsEntry("downHealthIndicator", Status.DOWN); + } + + @Test + void testContributorStatusesWithReactiveIndicators() { + initialize(NamedReactiveUpHealthConfiguration.class, NamedReactiveDownHealthConfiguration.class); + + Map statuses = new HashMap<>(); + Status status = healthCheckHandler.getStatus(new SimpleStatusAggregator(), statuses); + + assertThat(status).isEqualTo(Status.DOWN); + assertThat(statuses).containsEntry("reactiveUpHealthIndicator", Status.UP); + assertThat(statuses).containsEntry("reactiveDownHealthIndicator", Status.DOWN); + } + private void initialize(Class... configurations) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(configurations); healthCheckHandler.setApplicationContext(applicationContext); @@ -195,6 +222,33 @@ class EurekaHealthCheckHandlerTests { } + public static class NamedUpHealthConfiguration { + + @Bean + public HealthIndicator upHealthIndicator() { + return () -> Health.up().build(); + } + + } + + public static class NamedDownHealthConfiguration { + + @Bean + public HealthIndicator downHealthIndicator() { + return () -> Health.down().build(); + } + + } + + public static class NamedReactiveUpHealthConfiguration { + + @Bean + public ReactiveHealthIndicator reactiveUpHealthIndicator() { + return () -> Mono.just(Health.up().build()); + } + + } + public static class FatalHealthConfiguration { @Bean @@ -327,4 +381,15 @@ class EurekaHealthCheckHandlerTests { } + @Configuration + static class NamedReactiveDownHealthConfiguration { + + @Bean + CompositeReactiveHealthContributor namedReactiveDownHealthContributor() { + return CompositeReactiveHealthContributor.fromMap(Map.of("reactiveDownHealthIndicator", + (ReactiveHealthIndicator) () -> Mono.just(Health.down().build()))); + } + + } + }