spring-bootspring-boot-actuator

URI tag of http.client.requests metric as NONE in Spring Boot 3.0.X


After migrate from spring boot 2.7.5 to spring boot 3.0.1.

The tag URI on the metric http.client.requests have the value none in all cases.

We build the URI of the restTemplate like

URI uri = UriComponentsBuilder.fromUri(restTemplate.getUriTemplateHandler().expand("/hello"))
   .build()
   .toUri();

return restTemplate.getForObject(uri, String.class);

I'm used to do it this way since to add query params, I think it's more easy to read and to manage.

To reproduce it I created two small application in this repository one on spring boot 2.7.7 and an other one on 3.0.1

I figure out it's linked to the new Observability on spring 6 and linked to this issue on spring boot.

And Spring boot 2 customise the UriTemplateHandler and store the urlTemplate in ThreadLocal<Deque>. And use this deque for the tag uri.

From the documentation I know I could enhance the tag by providing a new @Bean ClientRequestObservationConvention.

I could also use org.springframework.web.client.RestTemplate#getForObject(java.lang.String, java.lang.Class, java.util.Map<java.lang.String,?>). like

return restTemplate.getForObject("/hello", String.class);

But I wonder if there is a way to get the previous behaviour and still used org.springframework.web.client.RestTemplate#getForEntity(java.net.URI, java.lang.Class).


Solution

  • Prior to Spring Boot 3 and the Observability improvements, RestTemplate would indeed use the full request URI for the "uri" tag. This is wrong, as URI values are not bounded in an application and can cause cardinality explosion in the metrics system.

    For example, a client could report uri tags with "/user/12", "/user/23" etc. The correct tag for this should be low cardinality and use a request pattern like "/user/{userId}".

    In Spring Boot 2.x, for compatibility reasons, we did not alter this behavior but added instead a MeterFilter that stops recording metrics once a threshold is reached. As of Spring Framework 6, the instrumentation is built in RestTemplate directly. This tag is only recorded if a RestTemplate method accepting a String template, like restTemplate.getForObject("/hello", String.class) as you've noticed.

    You can revert to the previous behavior by providing your own ClientRequestObservationConvention (see the migration wiki for more on that) but note that you'll also need to ensure that cardinality explosion doesn't become an issue in your application.

    See this Spring Boot issue that looks quite similar.