io.dropwizard.health.core.HealthCheckManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dropwizard-health Show documentation
Show all versions of dropwizard-health Show documentation
Provides a health check implementation that performs ongoing monitoring of an application's dependencies and includes
an endpoint that can be called by a load balancer to determine if the application is healthy and thus able to retrieve
traffic.
The newest version!
package io.dropwizard.health.core;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.health.HealthCheckRegistryListener;
import io.dropwizard.health.conf.HealthCheckConfiguration;
import io.dropwizard.health.conf.HealthCheckType;
import io.dropwizard.health.conf.Schedule;
import io.dropwizard.health.shutdown.ShutdownNotifier;
import io.dropwizard.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
public class HealthCheckManager implements HealthCheckRegistryListener, StateChangedCallback, HealthStatusChecker,
ShutdownNotifier {
private static final Logger log = LoggerFactory.getLogger(HealthCheckManager.class);
private static final Duration DEFAULT_SHUTDOWN_WAIT_PERIOD = Duration.seconds(15);
private static final boolean DEFAULT_INITIAL_OVERALL_STATE = true;
private final AtomicBoolean isAppAlive = new AtomicBoolean(true);
private final AtomicBoolean isAppHealthy = new AtomicBoolean(false);
private final AtomicInteger unhealthyCriticalHealthChecks = new AtomicInteger();
private final AtomicInteger unhealthyCriticalAliveChecks = new AtomicInteger();
private final HealthCheckScheduler scheduler;
private final Map checks;
private final Map configs;
private final MetricRegistry metrics;
private final String managerName;
private final Duration shutdownWaitPeriod;
private final boolean initialOverallState;
private final String aggregateHealthyName;
private final String aggregateUnhealthyName;
private volatile boolean shuttingDown = false;
@Deprecated
public HealthCheckManager(final List configs,
final HealthCheckScheduler scheduler,
final MetricRegistry metrics) {
this(configs, scheduler, metrics, DEFAULT_SHUTDOWN_WAIT_PERIOD, DEFAULT_INITIAL_OVERALL_STATE);
}
public HealthCheckManager(final List configs,
final HealthCheckScheduler scheduler,
final MetricRegistry metrics,
final Duration shutdownWaitPeriod,
final boolean initialOverallState) {
this(configs, scheduler, metrics, null, shutdownWaitPeriod, initialOverallState);
}
@Deprecated
public HealthCheckManager(final List configs,
final HealthCheckScheduler scheduler,
final MetricRegistry metrics,
final String managerName) {
this(configs, scheduler, metrics, managerName, DEFAULT_SHUTDOWN_WAIT_PERIOD, DEFAULT_INITIAL_OVERALL_STATE);
}
@Deprecated
public HealthCheckManager(final List configs,
final HealthCheckScheduler scheduler,
final MetricRegistry metrics,
final String managerName,
final Duration shutdownWaitPeriod,
final boolean initialOverallState) {
this(configs, scheduler, metrics, managerName, shutdownWaitPeriod, initialOverallState, new HashMap<>());
}
// Visible for testing
HealthCheckManager(final List configs,
final HealthCheckScheduler scheduler,
final MetricRegistry metrics,
final String managerName,
final Duration shutdownWaitPeriod,
final boolean initialOverallState,
final Map checks) {
this.configs = configs.stream()
.collect(Collectors.toMap(HealthCheckConfiguration::getName, Function.identity()));
this.scheduler = Objects.requireNonNull(scheduler);
this.metrics = Objects.requireNonNull(metrics);
this.managerName = managerName;
this.shutdownWaitPeriod = shutdownWaitPeriod;
this.initialOverallState = initialOverallState;
this.checks = Objects.requireNonNull(checks);
this.aggregateHealthyName = MetricRegistry.name("health", managerName, "aggregate", "healthy");
this.aggregateUnhealthyName = MetricRegistry.name("health", managerName, "aggregate", "unhealthy");
metrics.register(aggregateHealthyName, (Gauge) this::calculateNumberOfHealthyChecks);
metrics.register(aggregateUnhealthyName, (Gauge) this::calculateNumberOfUnhealthyChecks);
}
@Override
public void onHealthCheckAdded(final String name, final HealthCheck healthCheck) {
final HealthCheckConfiguration config = configs.get(name);
if (config == null) {
log.debug("ignoring registered health check that isn't configured: name={}", name);
return;
}
final Schedule schedule = config.getSchedule();
final HealthCheckType type = config.getType();
// type of 'alive' implies 'critical'
final boolean critical = (type == HealthCheckType.ALIVE) || config.isCritical();
final boolean initialState = config.isInitialState();
final State state = new State(name, schedule.getFailureAttempts(), schedule.getSuccessAttempts(), initialState, this);
final Counter healthyCheckCounter = metrics.counter(MetricRegistry.name("health", managerName, name, "healthy"));
final Counter unhealthyCheckCounter = metrics.counter(MetricRegistry.name("health", managerName, name, "unhealthy"));
final ScheduledHealthCheck check = new ScheduledHealthCheck(name, type, critical, healthCheck, schedule, state,
healthyCheckCounter, unhealthyCheckCounter);
checks.put(name, check);
// handle initial state of 'false' to ensure counts line up
if (!initialState && critical) {
handleCriticalHealthChange(name, type, false);
}
scheduler.scheduleInitial(check);
}
@Override
public void onHealthCheckRemoved(final String name, final HealthCheck healthCheck) {
scheduler.unschedule(name);
}
@Override
public void onStateChanged(final String name, final boolean isNowHealthy) {
log.debug("health check changed state: name={} state={}", name, isNowHealthy);
final ScheduledHealthCheck check = checks.get(name);
if (check == null) {
log.error("State changed for unconfigured health check: name={} state={}", name, isNowHealthy);
return;
}
if (check.isCritical()) {
handleCriticalHealthChange(check.getName(), check.getType(), isNowHealthy);
} else {
handleNonCriticalHealthChange(check.getName(), check.getType(), isNowHealthy);
}
scheduler.schedule(check, isNowHealthy);
}
protected void initializeAppHealth() {
this.isAppHealthy.set(initialOverallState);
}
private long calculateNumberOfHealthyChecks() {
return checks.values()
.stream()
.filter(ScheduledHealthCheck::isHealthy)
.count();
}
private long calculateNumberOfUnhealthyChecks() {
return checks.values()
.stream()
.filter(check -> !check.isHealthy())
.count();
}
private void handleCriticalHealthChange(final String name, final HealthCheckType type, final boolean isNowHealthy) {
if (isNowHealthy) {
log.info("A critical dependency is now healthy: name={}, type={}", name, type);
switch (type) {
case ALIVE:
updateCriticalStatus(isAppAlive, unhealthyCriticalAliveChecks.decrementAndGet());
return;
case READY:
if (!shuttingDown) {
updateCriticalStatus(isAppHealthy, unhealthyCriticalHealthChecks.decrementAndGet());
} else {
log.info("Status change is ignored during shutdown: name={}, type={}", name, type);
}
return;
}
} else {
log.error("A critical dependency is now unhealthy: name={}, type={}", name, type);
switch (type) {
case ALIVE:
updateCriticalStatus(isAppAlive, unhealthyCriticalAliveChecks.incrementAndGet());
return;
case READY:
updateCriticalStatus(isAppHealthy, unhealthyCriticalHealthChecks.incrementAndGet());
return;
}
}
log.warn("Unexpected health check type: type={}", type);
}
private void updateCriticalStatus(final AtomicBoolean status, final int count) {
status.set(count == 0);
log.debug("current status: unhealthy-critical={}", count);
}
private void handleNonCriticalHealthChange(final String name, final HealthCheckType type, final boolean isNowHealthy) {
if (isNowHealthy) {
log.info("A non-critical dependency is now healthy: name={}, type={}", name, type);
} else {
log.warn("A non-critical dependency is now unhealthy: name={}, type={}", name, type);
}
}
/**
* @deprecated use {@link #isHealthy()} instead.
*/
@Deprecated
public AtomicBoolean getIsAppHealthy() {
return isAppHealthy;
}
String getAggregateHealthyName() {
return aggregateHealthyName;
}
String getAggregateUnhealthyName() {
return aggregateUnhealthyName;
}
@Override
public boolean isHealthy() {
return isAppAlive.get() && isAppHealthy.get();
}
@Override
public boolean isHealthy(String type) {
if (HealthCheckType.ALIVE.name().equalsIgnoreCase(type)) {
return isAppAlive.get();
} else {
return isHealthy();
}
}
@Override
public void notifyShutdownStarted() throws Exception {
shuttingDown = true;
log.info("delayed shutdown: started (waiting {})", shutdownWaitPeriod);
// set healthy to false to indicate to the load balancer that it should not be in rotation for requests
isAppHealthy.set(false);
// sleep for period of time to give time for load balancer to realize requests should not be sent anymore
Thread.sleep(shutdownWaitPeriod.toMilliseconds());
log.info("delayed shutdown: finished");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy