Introducing Actuator Endpoints in Spring Boot 2.0

Engineering | Stéphane Nicoll | August 22, 2017 | ...

Spring Boot 2 brings important changes to Actuator and I am pleased, on behalf of the team, to give you a sneak peek to what’s coming in 2.0.0.M4.

Working on a major new release gives us the opportunity to revisit some of the public contracts and improve them. We quickly felt that the endpoint infrastructure was one of them: currently, the web endpoints in the Actuator are only supported with Spring MVC (no JAX-RS support). Also, creating a new endpoint that exposes several operations requires writing quite a lot of boiler plate: you need to write a main endpoint, the Spring MVC extension (as a @RestController), a JMX MBean and the necessary auto-configuration. As of Spring Boot 2 support for a "reactive" actuator became an obvious requirement that also brings several new challenges.

Endpoint infrastructure

Spring Boot 2 brings a brand new endpoint infrastructure that allows you to define one or several operations in a technology independent fashion with support for Spring MVC, Spring WebFlux and Jersey! Spring Boot 2 will have native support for Jersey and writing an adapter for another JAX-RS implementation should be easy as long as there is a way to programmatically register resources.

Let’s illustrate the new API with the existing /application/loggers MVC endpoint that allows you to control your logging configuration at runtime. This endpoint has three operations:

  • A main operation to list the current configuration.

  • A more narrowed operation to provide the configuration of a logger by name

  • A write operation to update the configuration of a particular logger.

This is how that endpoint looks like as of Spring Boot 2:

@Endpoint(id = "loggers")
public class LoggersEndpoint {

    @ReadOperation
    public Map<String, Object> loggers() { ... }

    @ReadOperation
    public LoggerLevels loggerLevels(@Selector String name) { ... }

    @WriteOperation
    public void configureLogLevel(@Selector String name, LogLevel configuredLevel) { ... }

}

The new @Endpoint annotation declares this type to be an endpoint with a mandatory, unique id. As we will see later, a bunch of properties will be automatically inferred from that. No additional code is required to expose this endpoint at /applications/loggers or as a org.springframework.boot:type=Endpoint,name=Loggers JMX MBean. Let’s find out why.

Web endpoint

This endpoint exposes three operations:

  • GET on /application/loggers: the configuration of all loggers (as it has no "selector" parameter):

  • GET on /application/loggers/{name}: the configuration of a named logger (using the name @Selector).

  • POST on /application/loggers/{name}: update the configuration of a named logger. This operation has an additional parameter that needs to be resolved as it is not used to further select the resource to update. In this particular case, a request body with a configuredLevel JSON attribute is expected, something like:

{
    "configuredLevel": "WARN"
}

Additional parameters of read operations will be automatically mapped from request attributes.

JMX MBean

No additional code is required to expose that endpoint as a JMX MBean. Because a typical JMX client (e.g. JConsole) does not have access to third party libraries, any non core types are automatically translated. In particular, LogLevel is an enum that is exposed as a String in the configureLogLevel JMX operation.

Cross-cutting features

The infrastructure allows us to provide cross-cutting features such as endpoint caching. At the moment, we offer a way to cache the result of the main operation (i.e. the loggers method above). Endpoint security is also in the works.

Extensions

If you need to express something in a technology-specific manner, you can write an extension for it. An example of that is the health endpoint that needs to change the response’s HTTP status code based on the computed Health. The health endpoint is pretty much what you would expect:

@Endpoint(id = "health")
public class HealthEndpoint {

    @ReadOperation
    public Health health() { ... }

}

The HealthWebEndpointExtension overrides the main read operation to produce a WebEndpointResponse<Health> rather that Health. This gives a chance to the web extension to provide web specific attributes to the response:

@WebEndpointExtension(endpoint = HealthEndpoint.class)
public class HealthWebEndpointExtension {

    private final HealthEndpoint delegate;

    public HealthWebEndpointExtension(HealthEndpoint delegate) { ... }

    @ReadOperation
    public WebEndpointResponse<Health> getHealth() {
        Health health = this.delegate.health();
        Integer status = // get http status based on current health
        return new WebEndpointResponse<>(health, status);
    }

}

Configuration

Each endpoint gets automatically a dedicated configuration namespace at endpoints.. The spring-boot-configuration-processor has been updated to automatically detect @Endpoint classes:

T0HFTViTS1C39qipMg Mhg

To configure an endpoint, all that’s required really is to expose it as a @Bean. There is a new @ConditionalOnEnabledEndpoint that makes sure that the endpoint is not created (or exposed) according to the current configuration:

@Bean
@ConditionalOnBean(LoggingSystem.class)
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem) {
	return new LoggersEndpoint(loggingSystem);
}

So, for instance, if endpoints.loggers.enabled=false is present in the environment, the condition will not match and the endpoint will not be exposed at all.

What’s next?

Because we now have a centralized infrastructure, we can be smarter as what to do depending on the runtime environment. If you’re running Actuator with WebFlux, it doesn’t necessarily assume that everything is non-blocking. In fact, operations that don’t return a Publisher (i.e. Mono or Flux) will be run on a dedicated thread pool so that they don’t block reactive operations. This work should land in master early next week.

We are also working on some reactive-specific contract for the actuator, i.e. a reactive Health endpoint that can execute the available HealthIndicators in a reactive fashion:

public interface ReactiveHealthIndicator {

    Mono<Health> health();

}

We are also considering migrating from a R/W model to a CRUD model (i.e. support for delete and create operations), please watch #10023 if you are interested.

If you want to give this new endpoint infrastructure a try we’d love to hear from you. To get started, generate an app on start.spring.io with Spring Boot 2.0.0.BUILD-SNAPSHOT.

Get the Spring newsletter

Stay connected with the Spring newsletter

Subscribe

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all