Log health contributor statuses in Eureka health checks

Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
This commit is contained in:
98001yash
2026-08-29 01:21:41 +05:30
parent cae667d82a
commit 70788002d0
2 changed files with 89 additions and 17 deletions
@@ -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, InstanceInfo.InstanceStatus> 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<String, Status> 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<String, Status> statuses) {
Status status = getStatus(statusAggregator, statuses);
return mapToInstanceStatus(status);
}
protected Status getStatus(StatusAggregator statusAggregator) {
Set<Status> statusSet = new HashSet<>();
for (HealthContributor contributor : healthContributors.values()) {
processContributor(statusSet, contributor);
protected Status getStatus(StatusAggregator statusAggregator, Map<String, Status> statuses) {
for (Map.Entry<String, HealthContributor> entry : healthContributors.entrySet()) {
processContributor(statuses, entry.getKey(), entry.getValue());
}
for (ReactiveHealthContributor contributor : reactiveHealthContributors.values()) {
processContributor(statusSet, contributor);
for (Map.Entry<String, ReactiveHealthContributor> entry : reactiveHealthContributors.entrySet()) {
processContributor(statuses, entry.getKey(), entry.getValue());
}
return statusAggregator.getAggregateStatus(statusSet);
return statusAggregator.getAggregateStatus(new HashSet<>(statuses.values()));
}
private void processContributor(Set<Status> statusSet, HealthContributor contributor) {
private void processContributor(Map<String, Status> 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<Status> statusSet, ReactiveHealthContributor contributor) {
private void processContributor(Map<String, Status> 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());
}
}
}
@@ -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<String, Status> 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<String, Status> 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())));
}
}
}