DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds
  • AI Agents in Java: Architecting Intelligent Health Data Systems
  • Bring Your Own Feed (BYOF): An Engineer's Guide to Effective Threat Intelligence
  • 5 Subtle Indicators Your Development Environment Is Under Siege

Trending

  • Intelligent Matching and Semantic Search for Marketplace Applications Using OpenAI and .NET
  • Zero-Downtime Deployments for Java Apps on Kubernetes
  • How to Format Articles for DZone
  • Testing AI-Infused Apps: A Dual-Layer Framework for AI Quality Assurance

Micronaut Mastery: Add Custom Health Indicators

Let's see how we can set up custom app health metrics for our microservices applications in Micronaut quickly and easily.

By 
Hubert Klein Ikkink user avatar
Hubert Klein Ikkink
·
Aug. 23, 18 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
8.8K Views

Join the DZone community and get the full member experience.

Join For Free

When we add the io.micronaut:management dependency to our Micronaut application, we get, among other things, a /health endpoint. We must enable it in our application configuration, where we can also configure how much information is shown and if we want to secure the endpoint. Micronaut has some built-in health indicators, some of which are only available based on certain conditions. For example, there is a disk space health indicator that will return a status of DOWN when the free disk space is less than a (configurable) threshold. If we would have one or more DataSource beans for database access in our application context, a health indicator is added to show if the database(s) are available or not.

We can also add our own health indicator that will show up in the /health endpoint. We must write a class that implements the HealthIndicator interface and add it to the application context. We could add some conditions to make sure the bean is loaded when needed. Micronaut also has the abstract AbstractHealthIndicator class, which can be used as base class for writing custom health indicators.

First, we must add the io.micronaut:management dependency to our compile classpath like in the following example Gradle build file:

// File: build.gradle
...
dependencies {
    ...
    compile "io.micronaut:management"
    ...
}
...

Next, we write a class that implements HealthIndicator or extends AbstractHealthIndicator. In the following example, we implement HealthIndicator and the method getResult. This health indicator will try to access a remote URL and will return the status UP when the URL is reachable and DOWN when the status code is invalid or an exception occurs. We also use the @Requires annotation to make sure the indicator is only loaded when the correct value is set for a configuration property and when the HealthPoint bean is available.

package mrhaki.micronaut;

import io.micronaut.health.HealthStatus;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.client.Client;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.management.health.indicator.HealthIndicator;
import io.micronaut.management.health.indicator.HealthResult;
import org.reactivestreams.Publisher;

import javax.inject.Singleton;
import java.util.Collections;

@Singleton
// Only create bean when configuration property
// endpoints.health.url.enabled equals true,
// and HealthEndpoint bean to expose /health endpoint is available.
@Requires(property = HealthEndpoint.PREFIX + ".url.enabled", value = "true")
@Requires(beans = HealthEndpoint.class)
public class RemoteUrlHealthIndicator implements HealthIndicator {

    /**
     * Name for health indicator.
     */
    private static final String NAME = "remote-url-health";

    /**
     * URL to check.
     */
    private static final String URL = "http://www.mrhaki.com/";

    /**
     * We use {@link RxHttpClient} to check if
     * URL is reachable.
     */
    private RxHttpClient client;

    /**
     * Inject client with URL to check.
     * 
     * @param client Client to check if URl is reachable.
     */
    public RemoteUrlHealthIndicator(@Client(URL) final RxHttpClient client) {
        this.client = client;
    }

    /**
     * Implementaton of {@link HealthIndicator#getResult()} where we
     * check if the url is reachable and return result based
     * on the HTTP status code.
     * 
     * @return Contains {@link HealthResult} with status UP or DOWN.
     */
    @Override
    public Publisher<HealthResult> getResult() {
        return client.exchange(HttpRequest.HEAD("/"))
                     .map(this::checkStatusCode)
                     .onErrorReturn(this::statusException);
    }

    /**
     * Check response status code and return status UP when code is
     * between 200 and 399, otherwise return status DOWN.
     * 
     * @param response Reponse with status code.
     * @return Result with checked URL in the details and status UP or DOWN.
     */
    private HealthResult checkStatusCode(HttpResponse<?> response) {
        final int statusCode = response.getStatus().getCode();
        final boolean statusOk = statusCode >= 200 && statusCode < 400;
        final HealthStatus healthStatus = statusOk ? HealthStatus.UP : HealthStatus.DOWN;

        // We use the builder API of HealthResult to create 
        // the health result with a details section containing
        // the checked URL and the health status.
        return HealthResult.builder(NAME, healthStatus)
                           .details(Collections.singletonMap("url", URL))
                           .build();
    }

    /**
     * Set status is DOWN when exception occurs checking URL status.
     * 
     * @param exception Exception thrown when checking status.
     * @return Result with exception in details in status DOWN.
     */
    private HealthResult statusException(Throwable exception) {
        // We use the build API of HealthResult to include
        // the original exception in the details and 
        // status is DOWN.
        return HealthResult.builder(NAME, HealthStatus.DOWN)
                           .exception(exception)
                           .build();
    }

}

Finally, we add configuration properties to our application.yml file:

# File: src/main/resources/application.yml
...
endpoints:
  health:
    enabled: true
    sensitive: false # non-secured endpoint
    details-visible: ANONYMOUS # show details for everyone
    url:
      enabled: true
...

Let's run our Micronaut application and invoke the /health endpoint. In the JSON response, we see the output of our custom health indicator:

...
        "remote-url-health": {
            "details": {
                "url": "http://www.mrhaki.com/"
            },
            "name": "micronaut-sample",
            "status": "UP"
        },
...

When we use a URL that is not available, we get the following output:

...
        "remote-url-health": {
            "details": {
                "error": "io.micronaut.http.client.exceptions.HttpClientResponseException: Not Found"
            },
            "name": "micronaut-sample",
            "status": "DOWN"
        },
...

Written with Micronaut 1.0.0.M4.

Indicator (metadata) Health (Apple)

Published at DZone with permission of Hubert Klein Ikkink. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds
  • AI Agents in Java: Architecting Intelligent Health Data Systems
  • Bring Your Own Feed (BYOF): An Engineer's Guide to Effective Threat Intelligence
  • 5 Subtle Indicators Your Development Environment Is Under Siege

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook