Disable and Replace Default DataSourceHealthIndicator

keharris picture keharris · Aug 16, 2016 · Viewed 8.3k times · Source

I am currently working with a health monitoring framework implemented using the Spring Boot Actuator "health" endpoint. The Actuator infrastructure supports the creation of custom health checks and also provides a number of built-in health checks; one of these is DataSourceHealthIndicator.

DataSourceHealthIndicator is part of the org.springframework.boot.actuate.health package, and is currently being used by our health framework to check the health of data sources. I have a need to use my own, slightly modified version of DataSourceHealthIndicator and to disable the "default."

I have tried the solutions suggested here and here, with no luck. What could I be doing wrong?

Thanks!


Edit: 8/18/2016, 3:38 PM EST

I've renamed my bean to dbHealthIndicator and added the following to my configuration class:

@Bean
public HealthIndicator dbHealthIndicator() {
     return new dbHealthIndicator();
}

I am now getting the following exceptions:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataAccessMapperFactory' defined in class path resource [udtContext.xml]

java.lang.RuntimeException: java.sql.SQLException: Unable to start the Universal Connection Pool: oracle.ucp.UniversalConnectionPoolException


Edit: 8/19/2016, 9:22 AM EST

This may help to demonstrate what I am trying to do. Currently, my /health endpoint returns something that looks like this:

dataSource: {
     status: "UP",
     database: "mySql",
     hello: "hello"
}

I would like for it to return something more like this, where the integer beside "result" is a status code returned by a stored procedure in my database:

dataSource: {
     status: "UP",
     database: "mySql",
     hello: "hello",
     result: 0
}

This is the method in DataSourceHealthIndicator.java that performs the check:

private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
    String product = getProduct();
    builder.up().withDetail("database", product);
    String validationQuery = getValidationQuery(product);
    if (StringUtils.hasText(validationQuery)) {
        try {
            // Avoid calling getObject as it breaks MySQL on Java 7
            List<Object> results = this.jdbcTemplate.query(validationQuery,
                    new SingleColumnRowMapper());
            Object result = DataAccessUtils.requiredSingleResult(results);
            builder.withDetail("hello", result);
        }
        catch (Exception ex) {
            builder.down(ex);
        }
    }
}

I need to add eight lines of code to this method, under builder.withDetail("hello", result);, to perform the call to the stored proc. I do not want to "decompile" the default class, and I am unable to override this method because it is private. I was thinking I could copy the DataSourceHealthIndicator.java code in my own bean, add my code, and rewire Spring to use this version instead, but I don't know if this is possible.

Answer

Shawn Clark picture Shawn Clark · Aug 17, 2016

Normally I look at the configuration for that HealthIndicator. In this case it is the HealthIndicatorAutoConfiguration.DataSourcesHealthIndicatorConfiguration. As the first linked suggestion stated. You need to name your custom bean dbHealthIndicator so that the @ConditionalOnMissingBean(name = "dbHealthIndicator") doesn't allow the default to be registered.

Providing some startup logs or details of what isn't working for you would help people troubleshoot.

Here is an example of how I got it to work:

@SpringBootApplication
public class StackoverflowWebmvcSandboxApplication {
    @Bean
    public HealthIndicator dbHealthIndicator() {
        return new HealthIndicator() {

            @Override
            public Health health() {
                return Health.status(Status.UP).withDetail("hello", "hi").build();
            }
        };
    }

    public static void main(String[] args) {
        SpringApplication.run(StackoverflowWebmvcSandboxApplication.class, args);
    }

    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    }
}

The /health endpoint then returned:

{
    "status": "UP",
    "db": {
        "status": "UP",
        "hello": "hi"
    },
    "diskSpace": {
        "status": "UP",
        "total": 127927316480,
        "free": 17191956480,
        "threshold": 10485760
    }
}