Engineering Software, Linux, and Observability. The website of Jack Neely.    

Prometheus Exemplars in Java Spring Boot

Java isn’t my favorite language to work in. However, I realized that to roll out a successful Observability plan that I needed good examples and most of the teams I work with create Spring Boot applications. So off I went to create a simple Java Spring Boot application that demonstrated a structured logging approach, metrics following the 4 Golden Signals pattern, and integration with basic tracing. The goal is to show how to pivot through one tool’s data to another. The problem was working with Prometheus’s Exemplars.

I swear that I’ve been looking at and thinking about how best to use Prometheus Exemplars since sometime in 2019. So I’ve been surprised to find that support for them is still very new. The Java world has only gotten some support for them this past year, 2021. However, the documentation indicates that they are well supported when pairing Prometheus’s client_java library with OpenTelemetry. I wanted to show off being able to pivot from a metric dashboard or an alert to the traces generated by the offending request.

When working with Java Spring Boot style applications I usually suggest folks implement Prometheus metrics by way of Micrometer. However, when I started this journey, support here didn’t look promising. So, I ripped out Micrometer from my example Spring Boot application and created equivalent instrumentation with Prometheus’s client_java library using version 0.12.0 and then the latest version 0.14.1. Exemplar support has been present since 0.11.0 so either of these versions should have been able to emit Exemplars. None did.

The first bit of troubleshooting I did was to make sure that the Prometheus client_java library was talking to me using the OpenMetrics 1.0 format. Although I’m very used to using curl to see and double check my metrics as the application is exporting them, we need to specify which format to use to get the OpenMetrics goodness and Exemplars.

$ curl -v -H 'Accept: application/openmetrics-text; version=1.0.0; charset=utf-8' http://localhost:8081/metrics

When using this curl command adding -v will show the headers the HTTP server sends back and will confirm if the returned format is OpenMetrics 1.0. The returned format was, indeed, OpenMetrics 1.0 but no Exemplars appeared.

I built Prometheus 2.30.1 on my laptop and made a quick test configuration to scrape my Java Spring Boot example application when it was running locally. Prometheus was definitely collecting metrics from my example application as expected. But no exemplars.

When running Java code with the OpenTelemetry Java Agent the Prometheus client_java library will recognize this and add trace_id and span_id Exemplars to Counter and Histogram metric types. The idea being that you don’t have to re-instrument your code to be able to link metrics to traces. It just works. There’s plenty of options to turn it off, but how do you turn it on? It’s on by default. So, to override this bit of logic my next step to troubleshoot this problem was to manually instrument Exemplars in my code. Per the documentation, this overrides any logic of when and if to add Exemplars to the output as they are manually instrumented. Cool!

So let’s make sure our Histogram object has Exemplars enabled:

prometheusHistogram =
  .help("Test Prometheus Client Library latency histogram")

Next, be sure to manually observe values for the Exemplars:

// Histogram type: Testing manual / explicit Exemplar support.
prometheusHistogram.observeWithExemplar(sw.getTotalTimeSeconds(), "span_foo", "0xdeadbeef", "trace_bar", "DEADBEEF");

Expecting to see Exemplars I ran this code, setup Prometheus to scrape the metrics again, and used curl with the headers set correctly. Yet again, no Exemplars were in the output. No Exemplars were recorded by Prometheus. Here, I double checked that I had Prometheus running with --enable-feature=exemplar-storage to be absolutely sure that Prometheus would show the Exemplars. But as they were not present in the curl output I wasn’t surprised when this produced no changes.

Here, I was stumped. I had verified versions of the code and libraries. I had triple checked that I was using the API correctly and, of course, it was compiling. Again, Java is not my favorite language to work in. However, I can usually bang enough rocks together to come up with a working Java patch or micro-service. I’m still not completely sure what a Bean is, but that shouldn’t affect my ability to make this example work. Why was this so difficult when the Grafana folks demonstrate this all the time? (Ok, that’s easy. Most of Grafana’s stuff is written in Golang which had the earliest support for new Prometheus features.)

Finally, I reached out to the Prometheus Developers’ Mailing List. Actually, a couple times as I was trying to assemble this for a flashy demo and then later when I actually had the time to figure this out. Fabian Stäber and I ended up exchanging multiple emails on what was going on here. Fabian added this support to the client_java library and is definitely the expert I needed.

In my build.gradle file I had the implementation dependencies stated like the following to include the Prometheus client_java library and the OpenTelemetry library so that I could directly query the current Span object to add additional span attributes as part of the example.

implementation 'io.prometheus:simpleclient:0.14.1'
implementation 'io.prometheus:simpleclient_hotspot:0.14.1'
implementation 'io.prometheus:simpleclient_httpserver:0.14.1'
implementation platform "io.opentelemetry:opentelemetry-bom:${project['otel-agent.version']}"
implementation 'io.opentelemetry:opentelemetry-api'

Apparently, the Spring Boot framework implicitly includes an old version of io.prometheus:simpleclient_common:0.10.0 for any number of reasons. But I didn’t have that specific library stated in my dependencies to override. This older version does not have Exemplar support. (How does Java run with miss-matched library dependencies again?) At Fabian’s suggestion, I added the following line to my build.gradle file:

implementation 'io.prometheus:simpleclient_common:0.14.1'

And like magic, the Exemplars were present in the curl output and in my local Prometheus server!

Being that examples of this working in Java are hard to find due to how new this support is, I wanted to give a full and complete example of a working setup. My Java Spring Boot example application shows how one might wrap their Spring Boot application with the OpenTelemetry Agent and get those sweet Exemplars to start showing up. Examples for manual instrumentation of Exemplars and a Counter that is auto-instrumented are both there. Perhaps others might find this useful!

In fact, if you remove the Gradle implementation dependency line above you can recreate the bug I was banging my head against.

How do we get back to Micrometer? That would be the ultimate goal, and although Micrometer doesn’t have direct support for Exemplars I was hoping that the auto-instrumentation features from the Prometheus libraries would at least provide the OpenTelemetry Span and Trace IDs. Unfortunately, this does not work. Sleuth 3.1.0 was very recently announced which includes underlying support for Exemplars in the Spring Boot world. This issue indicates that Micrometer will have Exemplar support via Sleuth in version 1.9 which shouldn’t be too far away as 1.8.1 is current as of this writing.

Interested in seeing a more complete Observability example with Java Spring Boot applications? Let me know in the comments!

 Previous  Up  Next

comments powered by Disqus