All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.dropwizard.util.health.UrlHealthCheck Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
package org.kiwiproject.dropwizard.util.health;

import static java.util.Objects.nonNull;
import static org.kiwiproject.base.KiwiPreconditions.requireNotBlank;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;
import static org.kiwiproject.jaxrs.KiwiResponses.successfulAlwaysClosing;
import static org.kiwiproject.metrics.health.HealthCheckResults.newHealthyResult;
import static org.kiwiproject.metrics.health.HealthCheckResults.newUnhealthyResultBuilder;

import com.codahale.metrics.health.HealthCheck;
import com.google.common.annotations.VisibleForTesting;
import jakarta.ws.rs.client.Client;
import lombok.extern.slf4j.Slf4j;
import org.kiwiproject.base.DefaultEnvironment;
import org.kiwiproject.base.KiwiEnvironment;
import org.kiwiproject.metrics.health.HealthStatus;

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;

/**
 * Simple health check to ensure that a URL can be reached and that it returns
 * a 2xx response.
 */
@Slf4j
public class UrlHealthCheck extends HealthCheck {

    @VisibleForTesting
    static final DateTimeFormatter FORMAT =
            DateTimeFormatter.ofPattern("HH:mm 'UTC on' yyyy-MM-dd");

    private final Client client;
    private final String description;
    private final String url;
    private final Supplier executionCondition;
    private final KiwiEnvironment kiwiEnvironment;

    /**
     * Create a new instance which always executes when health checks are called.
     *
     * @param client the HTTP client to use when connecting to the URL
     * @param description a human-friendly description of the URL (e.g., "prod web 1")
     * @param url the URL to check
     */
    public UrlHealthCheck(Client client, String description, String url) {
        this(client, description, url, () -> true);
    }

    /**
     * Create a new instance which executes when the {@code executionCondition} returns
     * {@code true}.
     * 

* This is useful when there are multiple instances of a service, and you only want * one of them to execute the health check, for example, the "leader" when using a * "Leader Latch" pattern. * * @param client the HTTP client to use when connecting to the URL * @param description a human-friendly description of the URL (e.g., "prod web 1") * @param url the URL to check * @param executionCondition whether to execute the health check when called */ public UrlHealthCheck(Client client, String description, String url, Supplier executionCondition) { this(client, description, url, executionCondition, new DefaultEnvironment()); } @VisibleForTesting UrlHealthCheck(Client client, String description, String url, Supplier executionCondition, KiwiEnvironment kiwiEnvironment) { this.client = requireNotNull(client, "client must not bu null"); this.description = requireNotBlank(description, "description must not be blank"); this.url = requireNotBlank(url, "url must not be blank"); this.executionCondition = requireNotNull(executionCondition, "executionCondition must not be null"); this.kiwiEnvironment = requireNotNull(kiwiEnvironment, "kiwiEnvironment must not be null"); } /** * Checks that the URL returns a 2xx response when the execution condition is true. *

* If the execution condition is false, skips the check and returns a "healthy" * result. If the execution condition throws an Exception, assumes that the * check should be performed and logs an error. */ @Override protected Result check() { if (shouldNotExecute()) { LOG.trace("executionCondition evaluated to false. Skip check and return 'healthy'"); return newHealthyResult("executionCondition evaluated to false; check skipped and reported as healthy"); } return checkHealth(); } private boolean shouldNotExecute() { return !shouldExecute(); } private boolean shouldExecute() { try { var result = executionCondition.get(); return nonNull(result) && result; } catch (Exception e) { LOG.error("executionCondition threw an exception. Assuming the health check should execute.", e); return true; } } private Result checkHealth() { var readableLastCheckTime = readableInstant(kiwiEnvironment.currentInstant()); try { var response = client.target(url).request().get(); var status = response.getStatus(); if (successfulAlwaysClosing(response)) { return newHealthyResult( "Got successful %d response from %s at %s (checked at: %s)", status, description, url, readableLastCheckTime); } else { return newUnhealthyResultBuilder(HealthStatus.WARN) .withMessage("Got unsuccessful %d response from %s at %s." + " It may not be functioning properly. (checked at: %s)", status, description, url, readableLastCheckTime) .build(); } } catch (Exception e) { return newUnhealthyResultBuilder(HealthStatus.CRITICAL, e) .withMessage("Got %s making call to %s at %s." + " It may be down or unreachable! (checked at: %s)", e.getClass().getName(), description, url, readableLastCheckTime) .build(); } } @VisibleForTesting static String readableInstant(Instant instant) { return FORMAT.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy