Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RestClient observation uri tag handling of query parameters #34176

Open
cj-florian-akos-szabo opened this issue Dec 29, 2024 · 1 comment
Open
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: waiting-for-triage An issue we've not yet triaged or decided on

Comments

@cj-florian-akos-szabo
Copy link

cj-florian-akos-szabo commented Dec 29, 2024

I have a project where I noticed a behavior in the http.client.requests metric coming from RestClient that's defined via such interface:

public interface ApiServiceClient {

    @GetExchange("/some/api/with/{variable}/and/params")
    ApiResultPojo makeApiRequest(
            @PathVariable String variable,
            @RequestParam(required = false, name=foo) String foo,
            @RequestParam(required = false, name=bar) String bar
    );
}

resulting in http.client.requests metric with such uri tags:

{
  "tag": "uri",
  "values": [
    "/some/api/with/{variable}/and/params",
    "/some/api/with/{variable}/and/params?{queryParam0}={queryParam0[0]}",
    "/some/api/with/{variable}/and/params?{queryParam0}={queryParam0[0]}&{queryParam1}={queryParam1[0]}"
  ]
},

depending on how many of the optional query params are non-null.

My issue is mainly with the cryptic names of the query params: queryParam0 & queryParam1. Could it be somehow possible to default these to use the name or value passed in the RequestParam annotation, and only use these basic placeholder names if custom name/value is not available? So in this case I'd expect the uri tag to include such items:

{
  "tag": "uri",
  "values": [
    "/some/api/with/{variable}/and/params",
    "/some/api/with/{variable}/and/params?foo={foo}",
    "/some/api/with/{variable}/and/params?foo={foo}&bar={bar}"
  ]
},

And currently, to work around this issue I am considering adding such a custom ClientRequestObservationConvention when I build the RestClient:

public class ExtendedClientRequestObservationConvention extends DefaultClientRequestObservationConvention {

    @Override
    public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
        return super.getLowCardinalityKeyValues(context).and(additionalTags(context));
    }

    protected KeyValues additionalTags(ClientRequestObservationContext context) {
        String fullUri = Objects.requireNonNull(context.getCarrier()).getURI().toString();
        String paramKeys = UriComponentsBuilder
                .fromUriString(fullUri).build()
                .getQueryParams().keySet()
                .stream()
                .sorted()
                .map(key -> key + "={" + key + "}")
                .collect(Collectors.joining("&"));
        String uriTemplate = Objects.requireNonNull(context.getUriTemplate()).split("\\?")[0];
        String uri = paramKeys.isEmpty() ? uriTemplate : uriTemplate + "?" + paramKeys;

        return KeyValues.of(KeyValue.of(URI, uri));
    }
}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 29, 2024
@bclozel bclozel added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Dec 29, 2024
@v-perfilev
Copy link
Contributor

Hi Florian,
nice workaround, it should work! I just believe that the right long-term solution would involve addressing this directly in the uri methods of DefaultRestClient:

@Override
public RequestBodySpec uri(String uriTemplate, Map<String, ?> uriVariables) {
    UriBuilder uriBuilder = uriBuilderFactory.uriString(uriTemplate);
    attribute(URI_TEMPLATE_ATTRIBUTE, uriBuilder.toUriString());
    return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables));
}

This is where the URI_TEMPLATE_ATTRIBUTE is set, and the only place it's used is for metrics in observatonContext.
I'd suggest introducing new interfaces: TemplateBuilder and TemplateBuilderFactory (in the org.springframework.web.util package), along with their default implementations. These could then be used in DefaultRestClient like:

@Override
public RequestBodySpec uri(String uriTemplate, Map<String, ?> uriVariables) {
    TemplateBuilder templateBuilder = templateBuilderFactory.uriTemplateWithVars(uriTemplate, uriVariables); // new template builder instead of uriBuilder
    attribute(URI_TEMPLATE_ATTRIBUTE, templateBuilder.toUriString());
    return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables));
}

With this approach the URI_TEMPLATE_ATTRIBUTE would look like /api/response/{id}?bar={bar} instead of /api/response/{id}?{queryParam0}={queryParam0[0]}.

@bclozel Do you think this feature is necessary? If so, I'd be happy to contribute and implement it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: waiting-for-triage An issue we've not yet triaged or decided on
Projects
None yet
Development

No branches or pull requests

4 participants