Description
Spring Boot: 3.1.5
Spring Framework: 6.0.13
In Spring 3 micrometer integration has been reworked to use Micrometer 1.10 Observation. It works in the default setup but in case there is a filter
(ExchangeFilterFunction
) that changes the underlining request, metric is reported with incorrect tags.
It happens because current implementation is initializing ClientRequestObservationContext
using the original ClientRequest
and any changes to the request after this point are not visible to the observation context
Similar scenario was supported in the previous version by adding custom filters before MetricsWebClientFilterFunction
that reported the same metric before.
Here is a test to reproduce the above issue. In this test original request uses POST
http method but filter is changing is to GET
. The metric is still reported with the original POST
method.
iterable contents differ at index [3], expected: <tag(method=GET)> but was: <tag(method=POST)>
Expected :tag(method=GET)
Actual :tag(method=POST)
@SpringBootTest(
properties = {"management.metrics.tags.service-name=test"}
)
class WebClientObservationTest {
@Autowired
private WebClient.Builder webClientBuilder;
@Autowired
private MeterRegistry meterRegistry;
@Test
void httpClientRequestsMetrics() {
var webClient = webClientBuilder
.filter(new ClientRequestExchangeFilter())
.build();
var req = webClient.post()
.uri("http://api.zippopotam.us/us/{zip}", 98121)
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(req)
.assertNext(res -> {
assertNotNull(res);
})
.verifyComplete();
Meter meter = meterRegistry.find("http.client.requests")
.meter();
assertNotNull(meter);
List<Tag> expectedTags = Arrays.asList(
Tag.of("client.name", "api.zippopotam.us"),
Tag.of("error", "none"),
Tag.of("exception", "none"),
Tag.of("method", "GET"),
Tag.of("outcome", "SUCCESS"),
Tag.of("service-name", "test"),
Tag.of("status", "200"),
Tag.of("uri", "/us/{zip}")
);
assertIterableEquals(expectedTags, meter.getId().getTags());
}
static class ClientRequestExchangeFilter implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
ClientRequest newRequest = ClientRequest.from(request)
.method(HttpMethod.GET)
.build();
return next.exchange(newRequest);
}
}
@TestConfiguration
public static class MockConfiguration {
@Bean
public MeterRegistry simpleMeterRegistry() {
return new SimpleMeterRegistry();
}
}
}