How to Setup Micrometer with Prometheus in Spring Boot 1.5
Overview
In this article, I will revisit a little bit my old article, but still valid, regarding monitoring your services. As you can see in my previous article, I wrote about monitoring service in Go application. This time a little different. I will monitor Spring Boot application by using Micrometer and wiring it into Prometheus endpoint.
Micrometer
So, what is micrometer. To answer this, I just quote from their website. Micrometer provides a simple facade over the instrumentation clients for the most popular monitoring systems, allowing you to instrument your JVM based application code without vendor lock-in¹. In other words, Ideally I can embed micrometer to my spring-boot application without any problem.
Without too much non-sense and talking, let start the step by step.
Step by step
The demo will expose a REST endpoint and we will let micrometer to instrument the metrics. The metrics result then will be expose to prometheus endpoint. Therefore, for the start we need two spring boot dependencies, which are web and actuator modules. Whenever the project setup is ready, we can start to implement micrometer into our service.
Step 1: add micrometer dependencies into pom.xml
1 2 3 4 5 6 7 8 9 10 11 |
<!-- micrometer --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-spring-legacy</artifactId> <version>${micrometer.version}</version> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>${micrometer.version}</version> </dependency> |
Note: in this article I am using micrometer version 1.0.2.
Step 2: Create a Configuration Bean to Register Your Metrics
By default, micrometer automatically configure three binders, which are JvmMemoryMetrics
, UptimeMetrics
and LogbackMetrics
. If you are happy with this default setup, you can skip this step. However, sometimes we want to have another binder, such as HystrixMetricsBinder
. Or we want to have our own tags in the metrics by registering MeterRegistryCustomizer
. All we need just register with @Bean
annotation in our bean configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import org.springframework.context.annotation.Bean; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.spring.autoconfigure.MeterRegistryCustomizer; /** * Configuration bean for metrics. * * @author ru-rocker * */ public class MetricsConfiguration { /** * Register common tags application instead of job. * This application tag is needed for Grafana dashboard. * * @return registry with registered tags. */ @Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> { registry.config().commonTags("application", "rurocker_metrics_service"); }; } } |
Note: I register application tag in order to display the metrics into Grafana micrometer dashboard.
Step 3: Configure Timer in Controller
By default, spring boot will auto configure interceptors to record the metrics in our endpoints. It registered as Timer
and produce a record with label http.server.requests
. And again, maybe it is not enough. Sometimes instead of rely everything by default configuration, there are custom needs. Such as we want to generate the result as a histogram. We can leverage this need by adding @Timed
annotation into our controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import java.util.concurrent.atomic.AtomicInteger; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.example.rurocker.metrics.dto.CounterDto; import io.micrometer.core.annotation.Timed; /** * Controller for counter. * * @author ru-rocker * */ @RestController public class CounterController { private AtomicInteger atomicInteger = new AtomicInteger(); @Timed(value = "get.counter.requests", histogram = true, percentiles = { 0.95, 0.99 }, extraTags = { "version", "v1" }) @GetMapping(path = "/counter") public CounterDto getCounter(@RequestParam("message") String message) { CounterDto dto = new CounterDto(); dto.setMessage(message); dto.setCounter(atomicInteger.incrementAndGet()); dto.setTimestamp(System.currentTimeMillis()); return dto; } } |
Note: @Timed
annotation will produce get.counter.requests instead of http.server.requests (see value’s value). Then we enable histogram and percentiles in two values (0.95 and 0.99). Lastly, we add another tag for this endpoint which is version tag.
Step 4: Optional: Change Your Prometheus Endpoint
By default, the endpoint will be /prometheus
. In case you want to change it, rename the property endpoints.prometheus.id
.
1 2 3 |
endpoints: prometheus: id: myprometheus |
Step 5: Run It
After you wait for a while, make some requests to your endpoint, then retrieve your metrics by accessing http://<your_ip>:<port>/myprometheus
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# make a request. curl -X GET http://localhost:8080/counter?message=hello | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 57 0 57 0 0 2966 0 --:--:-- --:--:-- --:--:-- 3000 { "counter" : 2, "message" : "hello", "timestamp" : 1521274007812 } # prometheus results curl -X GET http://localhost:8080/myprometheus |grep get_counter_request # HELP get_counter_requests_seconds # TYPE get_counter_requests_seconds histogram get_counter_requests_seconds{exception="None",method="GET",status="200",uri="/counter",version="v1",quantile="0.95",} 0.164626431 get_counter_requests_seconds{exception="None",method="GET",status="200",uri="/counter",version="v1",quantile="0.99",} 0.164626431 get_counter_requests_seconds_bucket{exception="None",method="GET",status="200",uri="/counter",version="v1",le="0.001",} 0.0 get_counter_requests_seconds_bucket{exception="None",method="GET",status="200",uri="/counter",version="v1",le="0.001048576",} 0.0 # ... some buckets are removed for clarity get_counter_requests_seconds_bucket{exception="None",method="GET",status="200",uri="/counter",version="v1",le="28.633115306",} 2.0 get_counter_requests_seconds_bucket{exception="None",method="GET",status="200",uri="/counter",version="v1",le="30.0",} 2.0 get_counter_requests_seconds_bucket{exception="None",method="GET",status="200",uri="/counter",version="v1",le="+Inf",} 2.0 get_counter_requests_seconds_count{exception="None",method="GET",status="200",uri="/counter",version="v1",} 2.0 get_counter_requests_seconds_sum{exception="None",method="GET",status="200",uri="/counter",version="v1",} 0.16958312 get_counter_requests_seconds_max{exception="None",method="GET",status="200",uri="/counter",version="v1",} 0.163803198 |
Conclusion
So simple, isn’t it? Just three steps and we have the metrics ready for our services. However, we need a better display for the metrics result. The metrics should be easy to see, read and understand. For that purpose, you need to export the result to Grafana and show them into your favorite dashboard. Also, you can set alerts based on your SLA or anything you like. You have the metrics. You have the data. They are all yours.
That’s all for now. Thank you for reading. You can download the sample on github.