Commit bf6f36a7 authored by Andy Wilkinson's avatar Andy Wilkinson

Apply any root URI to RestTemplate metric's URI tag

Previously, a root URI configured via RestTemplateBuilder's rootUri
method and RootUriTemplateHandler was not taken into account when
generated the URI tag for RestTemplate request metrics.

This commit updates MetricsClientHttpRequestInterceptor to be aware
of RootUriTemplateHandler and capture the URI template once the
root URI has been applied.

Fixes gh-25744
parent f8c1a73b
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -63,6 +63,16 @@ class RestTemplateMetricsConfigurationTests { ...@@ -63,6 +63,16 @@ class RestTemplateMetricsConfigurationTests {
}); });
} }
@Test
void restTemplateWithRootUriIsInstrumented() {
this.contextRunner.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
RestTemplateBuilder builder = context.getBean(RestTemplateBuilder.class);
builder = builder.rootUri("/root");
validateRestTemplate(builder, registry, "/root");
});
}
@Test @Test
void restTemplateCanBeCustomizedManually() { void restTemplateCanBeCustomizedManually() {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
...@@ -130,17 +140,22 @@ class RestTemplateMetricsConfigurationTests { ...@@ -130,17 +140,22 @@ class RestTemplateMetricsConfigurationTests {
} }
private void validateRestTemplate(RestTemplateBuilder builder, MeterRegistry registry) { private void validateRestTemplate(RestTemplateBuilder builder, MeterRegistry registry) {
RestTemplate restTemplate = mockRestTemplate(builder); this.validateRestTemplate(builder, registry, "");
}
private void validateRestTemplate(RestTemplateBuilder builder, MeterRegistry registry, String rootUri) {
RestTemplate restTemplate = mockRestTemplate(builder, rootUri);
assertThat(registry.find("http.client.requests").meter()).isNull(); assertThat(registry.find("http.client.requests").meter()).isNull();
assertThat(restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot").getStatusCode()) assertThat(restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot").getStatusCode())
.isEqualTo(HttpStatus.OK); .isEqualTo(HttpStatus.OK);
assertThat(registry.get("http.client.requests").tags("uri", "/projects/{project}").meter()).isNotNull(); assertThat(registry.get("http.client.requests").tags("uri", rootUri + "/projects/{project}").meter())
.isNotNull();
} }
private RestTemplate mockRestTemplate(RestTemplateBuilder builder) { private RestTemplate mockRestTemplate(RestTemplateBuilder builder, String rootUri) {
RestTemplate restTemplate = builder.build(); RestTemplate restTemplate = builder.build();
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate); MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
server.expect(requestTo("/projects/spring-boot")).andRespond(withStatus(HttpStatus.OK)); server.expect(requestTo(rootUri + "/projects/spring-boot")).andRespond(withStatus(HttpStatus.OK));
return restTemplate; return restTemplate;
} }
......
...@@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; ...@@ -29,6 +29,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.metrics.AutoTimer; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
...@@ -100,21 +101,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto ...@@ -100,21 +101,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
} }
UriTemplateHandler createUriTemplateHandler(UriTemplateHandler delegate) { UriTemplateHandler createUriTemplateHandler(UriTemplateHandler delegate) {
return new UriTemplateHandler() { if (delegate instanceof RootUriTemplateHandler) {
return ((RootUriTemplateHandler) delegate).withHandlerWrapper(CapturingUriTemplateHandler::new);
@Override }
public URI expand(String url, Map<String, ?> arguments) { return new CapturingUriTemplateHandler(delegate);
urlTemplate.get().push(url);
return delegate.expand(url, arguments);
}
@Override
public URI expand(String url, Object... arguments) {
urlTemplate.get().push(url);
return delegate.expand(url, arguments);
}
};
} }
private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) { private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) {
...@@ -123,6 +113,28 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto ...@@ -123,6 +113,28 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
.description("Timer of RestTemplate operation"); .description("Timer of RestTemplate operation");
} }
private static final class CapturingUriTemplateHandler implements UriTemplateHandler {
private final UriTemplateHandler delegate;
private CapturingUriTemplateHandler(UriTemplateHandler delegate) {
this.delegate = delegate;
}
@Override
public URI expand(String url, Map<String, ?> arguments) {
urlTemplate.get().push(url);
return this.delegate.expand(url, arguments);
}
@Override
public URI expand(String url, Object... arguments) {
urlTemplate.get().push(url);
return this.delegate.expand(url, arguments);
}
}
private static final class UrlTemplateThreadLocal extends NamedThreadLocal<Deque<String>> { private static final class UrlTemplateThreadLocal extends NamedThreadLocal<Deque<String>> {
private UrlTemplateThreadLocal() { private UrlTemplateThreadLocal() {
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,6 +18,7 @@ package org.springframework.boot.web.client; ...@@ -18,6 +18,7 @@ package org.springframework.boot.web.client;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -84,6 +85,17 @@ public class RootUriTemplateHandler implements UriTemplateHandler { ...@@ -84,6 +85,17 @@ public class RootUriTemplateHandler implements UriTemplateHandler {
return this.rootUri; return this.rootUri;
} }
/**
* Derives a new {@code RootUriTemplateHandler} from this one, wrapping its delegate
* {link UriTemplateHandler} by applying the given {@code wrapper}.
* @param wrapper the wrapper to apply to the delegate URI template handler
* @return the new handler
* @since 2.3.10
*/
public RootUriTemplateHandler withHandlerWrapper(Function<UriTemplateHandler, UriTemplateHandler> wrapper) {
return new RootUriTemplateHandler(this.rootUri, wrapper.apply(this.handler));
}
/** /**
* Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}. * Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}.
* @param restTemplate the {@link RestTemplate} to add the handler to * @param restTemplate the {@link RestTemplate} to add the handler to
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment