Reputation: 59
I would like to programmatically include health indicators into a actuator group. They only way to do this right now is in a *.properties
file.
Depending on what beans or other properties are found in the application it would be great to automatically or manually add in Health Indicators into the readiness/liveness groups. While it seems easy enough to setting the property, in many cases developers may forget to set it up or there are old projects that people will not known what health indicators may need to be configured.
If a datasource health indicator is enabled in the application, it would be nice to autoconfigure the that health indicator into the readiness group. However we wouldn't want this if the datasource health indicator is not active.
Thanks!
Upvotes: 5
Views: 2027
Reputation: 11
I had a similar need to instanciate and register at runtime some indicators and to add them dynamically to an actuator group.
The instanciation/registration part is performed in an implementation of ApplicationContextInitializer + SmartInitializingSingleton.
The provisioning of an actual actuator group is quite simple:
import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.acme.server.management.health.workers.WorkerStatusIndicatorAutoConfig.PROPERTIES_PREFIX;
import static com.acme.server.management.health.workers.WorkerStatusIndicatorInitializer.sanitize;
import static java.util.Optional.ofNullable;
import static org.springframework.util.ObjectUtils.isEmpty;
@Data
@Configuration
@ConfigurationProperties(prefix = PROPERTIES_PREFIX)
public class WorkerStatusIndicatorAutoConfig {
static final String PROPERTIES_PREFIX = "management.endpoint.health.workers";
String groupName = "workers";
@Autowired
HealthEndpointProperties healthEndpointProperties;
final Map<String, WorkerStatusIndicatorProperties> connections = new HashMap<>();
@PostConstruct
void init() {
var indicators = connections.keySet().stream().map(name -> name + "WorkerStatus").collect(Collectors.toSet())
var group = new HealthEndpointProperties.Group();
group.setInclude(indicators);
healthEndpointProperties.getGroup().put(groupName, group);
}
}
Upvotes: 0
Reputation: 4991
I think what I could come up with is what zlsmith86
has already suggested. Basically I created a custom CompositeHealthContributor
bean named readinessProbe
from all the HealthIndicator
and HealthContributor
beans that I might need, injected as Optional
and included in the composite contributor if they are present. Then the value of the management.endpoint.health.group.readiness.include
config can be set always to readinessProbe
, the name of the custom composite health contributor bean.
Custom composite health contributor:
package io.github.devatherock.config;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ReadinessProbeConfig {
@Bean
public CompositeHealthContributor readinessProbe(
@Qualifier("dbHealthContributor") Optional<HealthContributor> dbHealthContributor,
@Qualifier("mongoHealthContributor") Optional<HealthContributor> mongoHealthContributor,
Optional<DiskSpaceHealthIndicator> diskSpaceHealthIndicator) {
Map<String, HealthContributor> healthIndicatorMap = Arrays
.asList(dbHealthContributor, mongoHealthContributor, diskSpaceHealthIndicator)
.stream()
.filter(Optional::isPresent)
.collect(Collectors.toMap(
indicator -> indicator.get().getClass().getSimpleName(), Optional::get));
return CompositeHealthContributor.fromMap(healthIndicatorMap);
}
}
application.yml:
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: readinessProbe
Upvotes: 0
Reputation: 368
You need to add HealthIndicator beans to implement custom health indicators and then need to inject CustomHealthIndicator.
step 1: create custom health indicator in your application
import com.google.common.collect.ImmutableMap;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import java.util.Map;
/**
* Update #getStatus() to do your detailed application check (testing for databases, external services, queues, etc.)
* to represent your health check by indicating all its components are also functional (as opposed to a simple
* application-is-running check).
*/
public class EurekaHealthCheckHandler implements HealthCheckHandler, ApplicationContextAware, InitializingBean {
public final static ImmutableMap<Status, InstanceInfo.InstanceStatus> healthStatuses =
new ImmutableMap.Builder<Status, InstanceInfo.InstanceStatus>()
.put(Status.UNKNOWN, InstanceInfo.InstanceStatus.UNKNOWN)
.put(Status.OUT_OF_SERVICE, InstanceInfo.InstanceStatus.OUT_OF_SERVICE)
.put(Status.DOWN, InstanceInfo.InstanceStatus.DOWN)
.put(Status.UP, InstanceInfo.InstanceStatus.UP)
.build();
private final CompositeHealthIndicator healthIndicator;
private ApplicationContext applicationContext;
public EurekaHealthCheckHandler(HealthAggregator healthAggregator) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
this.healthIndicator = new CompositeHealthIndicator(healthAggregator);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
final Map<String, HealthIndicator> healthIndicators = applicationContext.getBeansOfType(HealthIndicator.class);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(entry.getKey(), entry.getValue());
}
}
@Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
Status status = healthIndicator.health().getStatus();
return healthStatuses.containsKey(status) ? healthStatuses.get(status) : InstanceInfo.InstanceStatus.UNKNOWN;
}
}
and then implement health indicators according to your needs
import lombok.extern.slf4j.Slf4j;
import com.application.services.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
/**
* Spring Cloud Health Indicator (for Eureka discovery). Sends a ping to Twilio and if sent successfully reports
* service as UP otherwise DOWN.
*/
@Component
@Slf4j
public class SmsHealthIndicator implements HealthIndicator {
@Autowired
private SmsService smsService;
/**
* Performs health check by getting first SMS from Twilio.
*/
@Override
public Health health() {
try {
smsService.testSampleMessage();
return Health.up().build();
} catch (Exception e) {
log.error("HealthCheck with Twilio failed", e);
return Health.down(e).build();
}
}
}
this should suffice your requirements
Upvotes: 0