It seems like metrics logic is different between DefaultRestTemplateExchangeTagsProvider (RestTemplate) and DefaultWebClientExchangeTagsProvider (WebClient). For the WebClient calls, the full expanded URI is exposed as the uri tag in the http.client.requests metric, while for the RestTemplate calls, the template is being used. This causes metrics to expose a very large number of dimensions for the uri tag and causes org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter to log:

Reached the maximum number of URI tags for 'http.client.requests'. Are you using 'uriVariables'?

Looking at the code WebClientExchangeTags, there seems to be some logic to look at a URI_TEMPLATE_ATTRIBUTE attribute in the request but it is not set by default (and the variable is private which makes it hard to use):

    /**
     * Creates a {@code uri} {@code Tag} for the URI path of the given {@code request}.
     * @param request the request
     * @return the uri tag
     */
    public static Tag uri(ClientRequest request) {
        String uri = (String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElseGet(() -> request.url().getPath());
        return Tag.of("uri", extractPath(uri));
    }

Would it be possible to have the uri tag in the WebClient metrics to be the template rather than the actual URI?

Comment From: wilkinsona

Spring Framework's DefaultWebClient will set the attribute when a request is made using a template. I can't explain why that apparently isn't happening in your case from the information that you have provided thus far. My best guess is that the request isn't being created with a template.

If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

Comment From: totof3110

Ohh... Thanks for pointing to this code. We were using the following code:

return webClient.get()
        .uri(uriBuilder -> uriBuilder
                .path("/api/users/{userId}")
                .build(Map.of("userId", userId)))
        .retrieve()
        .bodyToMono(User.class);

So even though it looks like this code is using a template, the request actually isn't created with a template? I'll try and update this code to use a different way and update here.

Comment From: totof3110

Indeed changing the code fixed the issue. Sorry for the confusion!

return webClient.get()
        .uri("/api/users/{userId}", Map.of("userId", userId))
        .retrieve()
        .bodyToMono(User.class);

Comment From: wilkinsona

Thanks for letting us know.