Skip to content

Introduce HealthIndicatorRegistry with support for invocation of HealthIndicators #4954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,7 +19,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -46,8 +45,9 @@
import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.TraceEndpoint;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.TraceRepository;
Expand Down Expand Up @@ -81,6 +81,7 @@
* @author Christian Dupuis
* @author Stephane Nicoll
* @author Eddú Meléndez
* @author Vedran Pavic
*/
@Configuration
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
Expand All @@ -94,7 +95,7 @@ public class EndpointAutoConfiguration {
private HealthAggregator healthAggregator = new OrderedHealthAggregator();

@Autowired(required = false)
private Map<String, HealthIndicator> healthIndicators = new HashMap<String, HealthIndicator>();
private HealthIndicatorRegistry healthIndicatorRegistry = new DefaultHealthIndicatorRegistry();

@Autowired(required = false)
private Collection<PublicMetrics> publicMetrics;
Expand All @@ -111,7 +112,7 @@ public EnvironmentEndpoint environmentEndpoint() {
@Bean
@ConditionalOnMissingBean
public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(this.healthAggregator, this.healthIndicators);
return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -27,18 +27,23 @@
import org.elasticsearch.client.Client;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.CassandraHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.DataSourceHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistryProperties;
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator;
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties;
import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator;
import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.JmsHealthIndicator;
import org.springframework.boot.actuate.health.MailHealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator;
Expand Down Expand Up @@ -84,6 +89,7 @@
* @author Stephane Nicoll
* @author Phillip Webb
* @author Tommy Ludwig
* @author Vedran Pavic
* @since 1.1.0
*/
@Configuration
Expand All @@ -100,6 +106,14 @@ public class HealthIndicatorAutoConfiguration {
@Autowired
private HealthIndicatorAutoConfigurationProperties configurationProperties = new HealthIndicatorAutoConfigurationProperties();

@Autowired
private HealthIndicatorRegistry healthIndicatorRegistry;

@Bean
public HealthIndicatorRegistryPostProcessor healthIndicatorRegistryPostProcessor() {
return new HealthIndicatorRegistryPostProcessor(this.healthIndicatorRegistry);
}

@Bean
@ConditionalOnMissingBean(HealthAggregator.class)
public OrderedHealthAggregator healthAggregator() {
Expand All @@ -116,6 +130,21 @@ public ApplicationHealthIndicator applicationHealthIndicator() {
return new ApplicationHealthIndicator();
}

@Configuration
@ConditionalOnMissingBean(HealthIndicatorRegistry.class)
@EnableConfigurationProperties(DefaultHealthIndicatorRegistryProperties.class)
public static class HealthIndicatorRegistryAutoConfiguration {

@Autowired
private DefaultHealthIndicatorRegistryProperties properties;

@Bean
public HealthIndicatorRegistry healthIndicatorRegistry() {
return new DefaultHealthIndicatorRegistry(this.properties);
}

}

/**
* Base class for configurations that can combine source beans using a
* {@link CompositeHealthIndicator}.
Expand Down Expand Up @@ -363,4 +392,51 @@ protected ElasticsearchHealthIndicator createHealthIndicator(Client client) {

}

/**
* A {@link BeanPostProcessor} that registers {@link HealthIndicator} beans with a
* {@link HealthIndicatorRegistry}.
*/
private static class HealthIndicatorRegistryPostProcessor implements BeanPostProcessor {
Copy link
Member

Choose a reason for hiding this comment

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

Why not just inject a map of beans straight into the registry implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It makes sense to have a single entry point for registering HealthIndicator instances, doesn't it? Also using this approach if users provide their own implementation of HealthIndicatorRegistry they would automatically get HealthIndicator Spring beans registered. Finally, backwards compatibility with current naming convention should be preserved:

The identifier for a given HealthIndicator is the name of the bean without the HealthIndicator suffix if it exists. In the example above, the health information will be available in an entry named my.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I still don't see the need for the post-processor. It feels more complex than the current code which is essentially doing the same thing

Copy link
Member

Choose a reason for hiding this comment

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

Also, if I don't want all of the HealthIndicator beans pushed into my custom registry, the post processor will do so anyway. If a custom registry is provided, it needs to be possible to have complete control over its contents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, fair point.


private HealthIndicatorRegistry healthIndicatorRegistry;

HealthIndicatorRegistryPostProcessor(HealthIndicatorRegistry healthIndicatorRegistry) {
this.healthIndicatorRegistry = healthIndicatorRegistry;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof HealthIndicator) {
postProcessHealthIndicator((HealthIndicator) bean, beanName);
}
return bean;
}

private void postProcessHealthIndicator(
HealthIndicator healthIndicator, String beanName) {
this.healthIndicatorRegistry.register(getKey(beanName), healthIndicator);
}

/**
* Turns the bean name into a key that can be used in the map of health information.
* @param name the bean name
* @return the key
*/
private String getKey(String name) {
int index = name.toLowerCase().indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,10 +18,10 @@

import java.util.Map;

import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;

Expand All @@ -31,11 +31,13 @@
* @author Dave Syer
* @author Christian Dupuis
* @author Andy Wilkinson
* @author Vedran Pavic
*/
@ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = true)
public class HealthEndpoint extends AbstractEndpoint<Health> {

private final HealthIndicator healthIndicator;
private final HealthAggregator healthAggregator;
private final HealthIndicatorRegistry healthIndicatorRegistry;

/**
* Time to live for cached result, in milliseconds.
Expand All @@ -45,19 +47,16 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
/**
* Create a new {@link HealthIndicator} instance.
* @param healthAggregator the health aggregator
* @param healthIndicators the health indicators
* @param healthIndicatorRegistry the health indicator registry
*/
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
HealthIndicatorRegistry healthIndicatorRegistry) {
super("health", false);
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
}
this.healthIndicator = healthIndicator;
Assert.notNull(healthIndicatorRegistry,
"healthIndicatorRegistry must not be null");
this.healthAggregator = healthAggregator;
this.healthIndicatorRegistry = healthIndicatorRegistry;
}

/**
Expand All @@ -78,19 +77,8 @@ public void setTimeToLive(long ttl) {
*/
@Override
public Health invoke() {
return this.healthIndicator.health();
Map<String, Health> healths = this.healthIndicatorRegistry.runHealthIndicators();
return this.healthAggregator.aggregate(healths);
}

/**
* Turns the bean name into a key that can be used in the map of health information.
* @param name the bean name
* @return the key
*/
private String getKey(String name) {
int index = name.toLowerCase().indexOf("healthindicator");
if (index > 0) {
return name.substring(0, index);
}
return name;
}
}
Loading