From bf6f36a783bd34f4c0858659dbe10334a6671f3f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 19 Mar 2021 11:58:10 +0000 Subject: [PATCH] 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 --- ...RestTemplateMetricsConfigurationTests.java | 25 ++++++++--- .../MetricsClientHttpRequestInterceptor.java | 42 ++++++++++++------- .../web/client/RootUriTemplateHandler.java | 14 ++++++- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java index 197c3e0681..69232dafe1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -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 void restTemplateCanBeCustomizedManually() { this.contextRunner.run((context) -> { @@ -130,17 +140,22 @@ class RestTemplateMetricsConfigurationTests { } 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(restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot").getStatusCode()) .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(); 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; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java index fabcbb322a..a5e4a540db 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.metrics.AutoTimer; +import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.core.NamedThreadLocal; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -100,21 +101,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto } UriTemplateHandler createUriTemplateHandler(UriTemplateHandler delegate) { - return new UriTemplateHandler() { - - @Override - public URI expand(String url, Map arguments) { - 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); - } - - }; + if (delegate instanceof RootUriTemplateHandler) { + return ((RootUriTemplateHandler) delegate).withHandlerWrapper(CapturingUriTemplateHandler::new); + } + return new CapturingUriTemplateHandler(delegate); } private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) { @@ -123,6 +113,28 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto .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 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> { private UrlTemplateThreadLocal() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java index ac684b828e..c6091c36ab 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.boot.web.client; import java.net.URI; import java.util.Map; +import java.util.function.Function; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -84,6 +85,17 @@ public class RootUriTemplateHandler implements UriTemplateHandler { 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 wrapper) { + return new RootUriTemplateHandler(this.rootUri, wrapper.apply(this.handler)); + } + /** * Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}. * @param restTemplate the {@link RestTemplate} to add the handler to