I was able to reproduce the behaviour you described. I think I initially missed this because I had the webflux instrumentation disabled.
Anyway, I think I found a solution which should work in your case:
private static class SpanAwareExchangeFunction implements ExchangeFunction {
private final ExchangeFunction delegate;
private final Consumer<Span> actions;
public SpanAwareExchangeFunction(ExchangeFunction delegate, Consumer<Span> actions) {
this.delegate = delegate;
this.actions = actions;
}
@Override
public Mono<ClientResponse> exchange(ClientRequest request) {
actions.accept(ElasticApm.currentSpan());
return delegate.exchange(request);
}
}
private static void doWebclientRequest() {
WebClient client = WebClient.builder()
.filter((clientRequest, next) ->
new SpanAwareExchangeFunction(next, span -> {
span.setLabel("label1", "from webclient filter");
}).exchange(clientRequest)
)
.build();
String response = client.get()
.uri("https://httpbin.org/get")
.exchangeToMono(response -> response.bodyToMono(String.class))
.block();
}
Why does this work? Our webclient instrumentation targets classes implementing the spring ExchangeFunction interface which also have ExchangeFunction in their name. This means in the code above the span is already started within SpanAwareExchangeFunction and therefore is accessible to you. It is important that SpanAwareExchangeFunction is not a lambda, so that the name contains "ExchangeFunction", because otherwise it will not be instrumented.