Search

Dark theme | Light theme

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.

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 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:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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.