Monday, November 20, 2017

Using Micrometer with Spring Boot 2

This is a very quick introduction to using the excellent Micrometer library to instrument a Spring Boot 2 based application and recording the metrics in Prometheus


Introduction

Micrometer provides a Java based facade over the client libraries that the different monitoring tools provide.

As an example consider Prometheus, if I were to integrate my Java application with Prometheus, I would have used the client library called Prometheus Client Java, used the data-structures(Counter, Gauge etc) to collect and provide data to Prometheus. If for any reason the monitoring system is changed, the code will have to be changed for the new system.

Micrometer attempts to alleviate this by providing a common facade that the applications use when writing code, binding to the monitoring system is purely a runtime concern and so changing Metrics system from Prometheus to say Datadog just requires changing a runtime library without needing any code changes.



Instrumenting a Spring Boot 2 Application

Nothing special needs to be done to get Micrometer support for a Spring Boot 2 based app, adding in the actuator starters pulls in Micrometer as a transitive dependency:

for eg. in a gradle based project this is sufficient:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-actuator')
    ...
}

Additionally since the intention is to send the data to Prometheus a dependency has to be pulled in which provides the necessary Micrometer SPI's.


dependencies {
    ...
    runtime("io.micrometer:micrometer-registry-prometheus")
    ...
}

By default Micrometer provides a set of intelligent bindings which instruments the Spring based Web and Webflux endpoints and adds in meters to collect the duration, count of calls. Additionally it also provides bindings to collect JVM metrics - memory usage, threadpool, etc.

An application property needs to be enabled to expose an endpoint which Prometheus will use to scrape the metrics data:

endpoints:
  prometheus:
    enabled: true

If the application is brought up at this point, the "/applications/prometheus" endpoint should be available showing a rich set of metrics, the following is a sample on my machine:


The default metrics is very rich and should cover most of the common set of metrics requirements of an application, if additional metrics is required it can easily added in as shown in the following code snippet:

class MessageHandler {
    
    private val counter = Metrics.counter("handler.calls", "uri", "/messages")
    
    fun handleMessage(req: ServerRequest): Mono<ServerResponse> {
        return req.bodyToMono<Message>().flatMap { m ->
            counter.increment()
            ...
...
}

Integrating with Prometheus

Prometheus can be configured to scrape data from the endpoint exposed by the Spring Boot2 app, a snippet of Prometheus configuration looks like this:

scrape_configs:
  - job_name: 'myapp'
    metrics_path: /application/prometheus
    static_configs:
      - targets: ['localhost:8080']

This is not really a production configuration, in a production setting it may better to use a Prometheus Push Gateway to broker the collection of metrics.

Prometheus provides a basic UI to preview the information that it scrapes, it can be accessed by default at port 9090. Here is a sample graph with the data produced during a load test:



Conclusion

Micrometer makes it very easy to instrument an application and collect a good set of basic metrics which can be stored and visualized in Prometheus. If you are interested in following this further, I have a sample application using Micrometer available here - https://github.com/bijukunjummen/boot2-load-demo

2 comments:

  1. Any idea on how to add custom Prometheus label to counter in Spring Boot 2.0 ? eg: https://github.com/prometheus/client_java#labels

    ReplyDelete
    Replies
    1. Not entirely sure @SureshG, may be better to ping @John Schneider over http://slack.micrometer.io/. I feel tags serve a similar purpose but could be good to check

      Delete