Loading...

November 12, 2015

Ratpacked: Add Health Checks

In the Ratpack core we can find the ratpack.health.HealthCheck interface. We can implement this interface to check for example if a mail server, that we need in our application, is available. Any objects that implement this interface and are registered in the Guice registry are handled by Ratpack. Ratpack also offers a HealthCheckHandler to output the results of all health checks or a single health check identified by a name. Instead of creating a new class that implements the HealthCheck interface we can also use the HealtCheck.of method. This method accepts an argument with the name of our check and a Closure or lambda expression with the code that does the checking.

Let's write a sample Ratpack application using the Groovy DSL. We first use the HealthCheck.of to implement a simple health check. We also using the HealthCheckHandler so we can request information about the health check.

// File: src/ratpack/Ratpack.groovy
import ratpack.exec.Promise
import ratpack.health.HealthCheck
import ratpack.health.HealthCheckHandler
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Add a simple HealtCheck implementation with 
        // the of method. The name of our health check is
        // "application". We simply return a Promise with
        // a HealthCheck.Result value. If we get here we assume
        // the application is UP and running.
        add HealthCheck.of('application') { Registry registry ->
            Promise.value(HealthCheck.Result.healthy("UP"))
        }
    }

    handlers {
        // Assign HealthCheckHandler to health/ endpoint.
        // Optionally we can provide the name of the health check
        // to get the results specific for that health check.
        get('health/:name?', new HealthCheckHandler()) 
    }
}

When we run the application and request the URL http://localhost:5050/health and http://localhost:5050/health/application we get the following output:

$ http localhost:5050/health
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache
connection: keep-alive
content-encoding: gzip
content-type: text/plain;charset=UTF-8
transfer-encoding: chunked

application : HEALTHY

$ http localhost:5050/health/application
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache
connection: keep-alive
content-encoding: gzip
content-type: text/plain;charset=UTF-8
transfer-encoding: chunked

application : HEALTHY

Now we create a new class that implements the HealthCheck interface. We write a health check implementation that checks if the free space on a drive is less than a given threshold. If there is less space the health check should return an unhealthy status:

// File: src/main/groovy/com/mrhaki/ratpack/healthcheck/DiskSpaceHealthCheck.groovy
package com.mrhaki.ratpack.healthcheck

import groovy.transform.CompileStatic
import ratpack.exec.Blocking
import ratpack.exec.Promise
import ratpack.health.HealthCheck
import ratpack.registry.Registry

import static org.apache.commons.io.FileUtils.byteCountToDisplaySize

/**
 * Ratpack {@link HealthCheck} implementation to check for free space 
 * on a disk. If the available free space is less than a given
 * threshold value than the check return unhealthy.
 */
@CompileStatic
class DiskSpaceHealthCheck implements HealthCheck {

    /**
     *  Default disk to check for free space is the local
     * disk our Ratpack app is running. 
     */
    File path = new File(".")

    /**
     *  Default threshold is 50 MB. 
     */
    long threshold = 50L * 1024 * 1024

    /**
     * Name for health check. Name must be unique
     * in Ratpack application. To add more instances
     * of this class use different names.
     */
    String name = 'diskSpace'

    /**
     * Check available free space for the given {@link #path} value. Compare 
     * this with the configured {@link #threshold} value. If available free
     * space is less than threshold return unhealthy result.
     * Otherwise return a healthy status.
     * 
     * @param registry Ratpack registry.
     * @return Unhealthy result when available free space less than threshold, otherwise healthy.
     * @throws Exception Something goes wrong.
     */
    @Override
    Promise<HealthCheck.Result> check(final Registry registry) throws Exception {
        Blocking.get {

            // Get available free space. Operation is potentially blocking
            // so inside a Blocking.get{} block.
            path.freeSpace

        }.map { Long diskFreeSpace ->

            // Format bytes to readable format with KB, MB, GB, etc.
            final String diskFreeSpaceFormatted = byteCountToDisplaySize(diskFreeSpace)
            final String thresholdFormatted = byteCountToDisplaySize(threshold)

            // Check if available free space is above given threshold.
            if (diskFreeSpace >= threshold) {
                // Everything is ok.
                HealthCheck.Result.healthy(
                        'Available: %s (threshold: %s)',
                        diskFreeSpaceFormatted,
                        thresholdFormatted)
            } else {
                // Available free space is below given threshold.
                // Create message with information
                // and signal as unhealthy.
                HealthCheck.Result.unhealthy(
                        'Free disk space below threshold. Available: %s (threshold: %s)',
                        diskFreeSpaceFormatted,
                        thresholdFormatted)
            }
        }
    }
}

We only have to add this class to our registry and Ratpack will pick it up:

// File: src/ratpack/Ratpack.groovy
import com.mrhaki.ratpack.healthcheck.DiskSpaceHealthCheck
import ratpack.exec.Promise
import ratpack.health.HealthCheck
import ratpack.health.HealthCheckHandler
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new DiskSpaceHealthCheck(threshold: 50L * 1024 * 1024 * 1024 /* 50GB */)
        add HealthCheck.of('application') { Registry registry ->
            Promise.value(HealthCheck.Result.healthy("UP"))
        }
    }

    handlers {
        get('health/:name?', new HealthCheckHandler()) 
    }
}

We run the application and check the results of the health endpoint:

$ http localhost:5050/health
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache
connection: keep-alive
content-encoding: gzip
content-type: text/plain;charset=UTF-8
transfer-encoding: chunked

application : HEALTHY
diskSpace : UNHEALTHY [Free disk space below threshold. Available: 41 GB (threshold: 50 GB)]

$ http localhost:5050/health/diskSpace
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache
connection: keep-alive
content-encoding: gzip
content-type: text/plain;charset=UTF-8
transfer-encoding: chunked

diskSpace : UNHEALTHY [Free disk space below threshold. Available: 41 GB (threshold: 50 GB)]

Written with Ratpack 1.1.1.