Search code examples
javaspringspring-boothealth-monitoringspring-boot-actuator

Disable and Replace Default DataSourceHealthIndicator


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.


Solution

  • 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
        }
    }