Grails Goodness: Adding Health Check Indicators
With Grails 3 we also get Spring Boot Actuator. We can use Spring Boot Actuator to add some production-ready features for monitoring and managing our Grails application. One of the features is the addition of some endpoints with information about our application. By default we already have a /health
endpoint when we start a Grails (3+) application. It gives back a JSON response with status UP. Let's expand this endpoint and add a disk space, database and url health check indicator.
We can set the application property endpoints.health.sensitive
to false
(securing these endpoints will be another blog post) and we automatically get a disk space health indicator. The default threshold is set to 10MB, so when the disk space is lower than 10MB the status is set to DOWN. The following snippet shows the change in the grails-app/conf/application.yml
to set the property:
...
---
endpoints:
health:
sensitive: false
...
If we invoke the /health
endpoint we get the following output:
{
"status": "UP",
"diskSpace": {
"status": "UP",
"free": 97169154048,
"threshold": 10485760
}
}
If we want to change the threshold
we can create a Spring bean of type DiskSpaceHealthIndicatorProperties
and name diskSpaceHealthIndicatorProperties
to override the default bean. Since Grails 3 we can override doWithSpring
method in the Application
class to define Spring beans:
package healthcheck
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application)
}
@Override
Closure doWithSpring() {
{ ->
diskSpaceHealthIndicatorProperties(DiskSpaceHealthIndicatorProperties) {
// Set threshold to 250MB.
threshold = 250 * 1024 * 1024
}
}
}
}
Spring Boot Actuator already contains implementations for checking SQL databases, Mongo, Redis, Solr and RabbitMQ. We can activate those when we add them as Spring beans to our application context. Then they are automatically picked up and added to the results of the /health
endpoint. In the following example we create a Spring bean databaseHealth
of type DataSourceHealthIndicator
:
package healthcheck
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.boot.actuate.health.DataSourceHealthIndicator
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application)
}
@Override
Closure doWithSpring() {
{ ->
// Configure data source health indicator based
// on the dataSource in the application context.
databaseHealthCheck(DataSourceHealthIndicator, dataSource)
diskSpaceHealthIndicatorProperties(DiskSpaceHealthIndicatorProperties) {
threshold = 250 * 1024 * 1024
}
}
}
}
To create our own health indicator class we must implement the HealthIndicator
interface. The easiest way is to extend the AbstractHealthIndicator
class and override the method doHealthCheck
. It might be nice to have a health indicator that can check if a URL is reachable. For example if we need to access a REST API reachable through HTTP in our application we can check if it is available.
package healthcheck
import org.springframework.boot.actuate.health.AbstractHealthIndicator
import org.springframework.boot.actuate.health.Health
class UrlHealthIndicator extends AbstractHealthIndicator {
private final String url
private final int timeout
UrlHealthIndicator(final String url, final int timeout = 10 * 1000) {
this.url = url
this.timeout = timeout
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
final HttpURLConnection urlConnection =
(HttpURLConnection) url.toURL().openConnection()
final int responseCode =
urlConnection.with {
requestMethod = 'HEAD'
readTimeout = timeout
connectTimeout = timeout
connect()
responseCode
}
// If code in 200 to 399 range everything is fine.
responseCode in (200..399) ?
builder.up() :
builder.down(
new Exception(
"Invalid responseCode '${responseCode}' checking '${url}'."))
}
}
In our Application
class we create a Spring bean for this health indicator so it is picked up by the Spring Boot Actuator code:
package healthcheck
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.boot.actuate.health.DataSourceHealthIndicator
import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application)
}
@Override
Closure doWithSpring() {
{ ->
// Create instance for URL health indicator.
urlHealthCheck(UrlHealthIndicator, 'http://intranet', 2000)
databaseHealthCheck(DataSourceHealthIndicator, dataSource)
diskSpaceHealthIndicatorProperties(DiskSpaceHealthIndicatorProperties) {
threshold = 250 * 1024 * 1024
}
}
}
}
Now when we run our Grails application and access the /health
endpoint we get the following JSON:
{
"status": "DOWN",
"urlHealthCheck": {
"status": "DOWN",
"error": "java.net.UnknownHostException: intranet",
},
"databaseHealthCheck": {
"status": "UP",
"database": "H2",
"hello": 1,
},
"diskSpace": {
"status": "UP",
"free": 96622411776,
"threshold": 262144000
},
}
Notice that the URL health check fails so the complete status is set to DOWN.
Written with Grails 3.0.1.