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.