Affects: 6.0.x

I'm using WebClient (through an http interface client) to call the /actuator/health endpoint of other services. I don't want to observe these calls so I'm trying to write an ObservationPredicate to filter out requests that are made to /actuator/... (or any arbitrary endpoints).

Something like this:

@Bean
ObservationPredicate actuatorClientContextPredicate() {
    return (name, context) -> {
        if (name.equals("http.client.requests") && context instanceof ClientRequestObservationContext clientContext) {
            return !clientContext.getRequest().url().getPath().startsWith("/actuator");
        }
        else {
            return true;
        }
    };
}

The issue is the following: in DefaultWebClient, the Observation is created before the carrier/request would be set on the context and since the ObservationPredicate is tested when the Observation is created, the predicate will not see the request or the carrier.

https://github.com/spring-projects/spring-framework/blob/da088e58c31c47835e5eb572bb4b0263a57acb74/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java#L455-L468

Is it possible to add details to the context that I can use to determine which endpoint is called?

I think it worth mentioning that this works on the client side with Feign and on the server side with WebMVC, see here.

Comment From: bclozel

I had a look and just remembered that @marcingrzejszczak and I looked at this.

The tracing support needs to mutate the HTTP client request before it's sent to add propagation information as request headers. Once built, the org.springframework.web.reactive.function.client.ClientRequest is immutable.

If we start the observation after the request has been created, it's no longer possible to mutate it and add propagation information to it. org.springframework.web.reactive.function.client.ClientRequestObservationContext#getRequest is marked as @Nullable for this very reason. I think it somehow make sense to provide a single version of the request (the one that's actually sent to the remote server) to the context, otherwise we would share the request before headers were modified.

I'm closing this for now, but we can reopen if we find a better way to do this.

Comment From: jonatan-ivanov

It does not need to be that request instance or a request instance at all. I think any of these should solve the issue (they have their own advantages and disadvantages): 1. Add details to the context directly: context.put("url", "url") 2. Register the carrier (builder) before start so that users can build a request intance for themselves 3. Register both the carrier and the request and re-set the request after the builder was modified