Skip to content

Commit 8b4a20d

Browse files
committed
Use Observation infrastructure for instrumenting WebClient
As of spring-projects/spring-framework#28341, `WebClient` is instrumented directly for `Observation`. This commit removes the custom `ExchangeFilterFunction` that previously instrumented the client for metrics. As a result, the relevant tag providers are now deprecated and adapted as `ObservationConvention` for the time being. Closes gh-32518
1 parent f0e40bb commit 8b4a20d

21 files changed

+400
-527
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsProperties.java

-10
Original file line numberDiff line numberDiff line change
@@ -149,16 +149,6 @@ public static class ClientRequest {
149149
*/
150150
private String metricName = "http.client.requests";
151151

152-
/**
153-
* Auto-timed request settings.
154-
*/
155-
@NestedConfigurationProperty
156-
private final AutoTimeProperties autotime = new AutoTimeProperties();
157-
158-
public AutoTimeProperties getAutotime() {
159-
return this.autotime;
160-
}
161-
162152
@DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name")
163153
public String getMetricName() {
164154
return this.metricName;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
18+
19+
import io.micrometer.common.KeyValues;
20+
import io.micrometer.core.instrument.Tag;
21+
import io.micrometer.observation.Observation;
22+
23+
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
24+
import org.springframework.web.reactive.function.client.ClientObservationContext;
25+
import org.springframework.web.reactive.function.client.ClientObservationConvention;
26+
import org.springframework.web.reactive.function.client.ClientRequest;
27+
import org.springframework.web.reactive.function.client.WebClient;
28+
29+
/**
30+
* Adapter class that applies {@link WebClientExchangeTagsProvider} tags as a
31+
* {@link ClientObservationConvention}.
32+
*
33+
* @author Brian Clozel
34+
*/
35+
@SuppressWarnings("deprecation")
36+
class ClientObservationConventionAdapter implements ClientObservationConvention {
37+
38+
private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
39+
40+
private final String metricName;
41+
42+
private final WebClientExchangeTagsProvider tagsProvider;
43+
44+
ClientObservationConventionAdapter(String metricName, WebClientExchangeTagsProvider tagsProvider) {
45+
this.metricName = metricName;
46+
this.tagsProvider = tagsProvider;
47+
}
48+
49+
@Override
50+
public boolean supportsContext(Observation.Context context) {
51+
return context instanceof ClientObservationContext;
52+
}
53+
54+
@Override
55+
public KeyValues getLowCardinalityKeyValues(ClientObservationContext context) {
56+
KeyValues keyValues = KeyValues.empty();
57+
mutateClientRequest(context);
58+
Iterable<Tag> tags = this.tagsProvider.tags(context.getCarrier(), context.getResponse(),
59+
context.getError().orElse(null));
60+
for (Tag tag : tags) {
61+
keyValues = keyValues.and(tag.getKey(), tag.getValue());
62+
}
63+
return keyValues;
64+
}
65+
66+
/*
67+
* {@link WebClientExchangeTagsProvider} relies on a request attribute to get the URI
68+
* template, we need to adapt to that.
69+
*/
70+
private static void mutateClientRequest(ClientObservationContext context) {
71+
ClientRequest clientRequest = ClientRequest.from(context.getCarrier())
72+
.attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build();
73+
context.setCarrier(clientRequest);
74+
}
75+
76+
@Override
77+
public KeyValues getHighCardinalityKeyValues(ClientObservationContext context) {
78+
return KeyValues.empty();
79+
}
80+
81+
@Override
82+
public String getName() {
83+
return this.metricName;
84+
}
85+
86+
}
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import io.micrometer.core.instrument.MeterRegistry;
2020
import io.micrometer.core.instrument.config.MeterFilter;
21+
import io.micrometer.observation.Observation;
22+
import io.micrometer.observation.ObservationRegistry;
2123

2224
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
2325
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
@@ -30,33 +32,43 @@
3032
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
3133
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
3234
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
3336
import org.springframework.context.annotation.Import;
3437
import org.springframework.core.annotation.Order;
3538

3639
/**
37-
* {@link EnableAutoConfiguration Auto-configuration} for HTTP client-related metrics.
40+
* {@link EnableAutoConfiguration Auto-configuration} for HTTP client-related
41+
* observations.
3842
*
3943
* @author Jon Schneider
4044
* @author Phillip Webb
4145
* @author Stephane Nicoll
4246
* @author Raheela Aslam
43-
* @since 2.1.0
47+
* @author Brian Clozel
48+
* @since 3.0.0
4449
*/
4550
@AutoConfiguration(after = { ObservationAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
4651
RestTemplateAutoConfiguration.class, WebClientAutoConfiguration.class })
47-
@ConditionalOnClass(MeterRegistry.class)
48-
@ConditionalOnBean(MeterRegistry.class)
49-
@Import({ RestTemplateObservationConfiguration.class, WebClientMetricsConfiguration.class })
50-
public class HttpClientMetricsAutoConfiguration {
51-
52-
@Bean
53-
@Order(0)
54-
public MeterFilter metricsHttpClientUriTagFilter(MetricsProperties properties) {
55-
String metricName = properties.getWeb().getClient().getRequest().getMetricName();
56-
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String
57-
.format("Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?", metricName));
58-
return MeterFilter.maximumAllowableTags(metricName, "uri", properties.getWeb().getClient().getMaxUriTags(),
59-
denyFilter);
52+
@ConditionalOnClass(Observation.class)
53+
@ConditionalOnBean(ObservationRegistry.class)
54+
@Import({ RestTemplateObservationConfiguration.class, WebClientObservationConfiguration.class })
55+
public class HttpClientObservationsAutoConfiguration {
56+
57+
@Configuration(proxyBeanMethods = false)
58+
@ConditionalOnClass(MeterRegistry.class)
59+
@ConditionalOnBean(MeterRegistry.class)
60+
class MeterFilterConfiguration {
61+
62+
@Bean
63+
@Order(0)
64+
MeterFilter metricsHttpClientUriTagFilter(MetricsProperties properties) {
65+
String metricName = properties.getWeb().getClient().getRequest().getMetricName();
66+
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String.format(
67+
"Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?", metricName));
68+
return MeterFilter.maximumAllowableTags(metricName, "uri", properties.getWeb().getClient().getMaxUriTags(),
69+
denyFilter);
70+
}
71+
6072
}
6173

6274
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateObservationConfiguration.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
1818

19-
import io.micrometer.observation.Observation;
2019
import io.micrometer.observation.ObservationRegistry;
2120

2221
import org.springframework.beans.factory.ObjectProvider;
@@ -39,8 +38,8 @@
3938
* @author Brian Clozel
4039
*/
4140
@Configuration(proxyBeanMethods = false)
42-
@ConditionalOnClass({ RestTemplate.class, Observation.class })
43-
@ConditionalOnBean({ RestTemplateBuilder.class, ObservationRegistry.class })
41+
@ConditionalOnClass(RestTemplate.class)
42+
@ConditionalOnBean(RestTemplateBuilder.class)
4443
@SuppressWarnings("deprecation")
4544
class RestTemplateObservationConfiguration {
4645

Original file line numberDiff line numberDiff line change
@@ -16,41 +16,42 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
1818

19-
import io.micrometer.core.instrument.MeterRegistry;
19+
import io.micrometer.observation.ObservationRegistry;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
22-
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Client.ClientRequest;
23-
import org.springframework.boot.actuate.metrics.web.reactive.client.DefaultWebClientExchangeTagsProvider;
24-
import org.springframework.boot.actuate.metrics.web.reactive.client.MetricsWebClientCustomizer;
23+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
24+
import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer;
2525
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
27-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2827
import org.springframework.context.annotation.Bean;
2928
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.web.reactive.function.client.ClientObservationConvention;
30+
import org.springframework.web.reactive.function.client.DefaultClientObservationConvention;
3031
import org.springframework.web.reactive.function.client.WebClient;
3132

3233
/**
3334
* Configure the instrumentation of {@link WebClient}.
3435
*
3536
* @author Brian Clozel
36-
* @author Stephane Nicoll
3737
*/
3838
@Configuration(proxyBeanMethods = false)
3939
@ConditionalOnClass(WebClient.class)
40-
class WebClientMetricsConfiguration {
40+
@SuppressWarnings("deprecation")
41+
class WebClientObservationConfiguration {
4142

4243
@Bean
43-
@ConditionalOnMissingBean
44-
WebClientExchangeTagsProvider defaultWebClientExchangeTagsProvider() {
45-
return new DefaultWebClientExchangeTagsProvider();
46-
}
47-
48-
@Bean
49-
MetricsWebClientCustomizer metricsWebClientCustomizer(MeterRegistry meterRegistry,
50-
WebClientExchangeTagsProvider tagsProvider, MetricsProperties properties) {
51-
ClientRequest request = properties.getWeb().getClient().getRequest();
52-
return new MetricsWebClientCustomizer(meterRegistry, tagsProvider, request.getMetricName(),
53-
request.getAutotime());
44+
ObservationWebClientCustomizer metricsWebClientCustomizer(ObservationRegistry observationRegistry,
45+
ObservationProperties observationProperties,
46+
ObjectProvider<WebClientExchangeTagsProvider> optionalTagsProvider, MetricsProperties metricsProperties) {
47+
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
48+
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
49+
String name = (observationName != null) ? observationName : metricName;
50+
WebClientExchangeTagsProvider tagsProvider = optionalTagsProvider.getIfAvailable();
51+
ClientObservationConvention observationConvention = (tagsProvider != null)
52+
? new ClientObservationConventionAdapter(name, tagsProvider)
53+
: new DefaultClientObservationConvention(name);
54+
return new ObservationWebClientCustomizer(observationRegistry, observationConvention);
5455
}
5556

5657
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+15-3
Original file line numberDiff line numberDiff line change
@@ -1905,16 +1905,28 @@
19051905
{
19061906
"name": "management.metrics.web.client.request.autotime.enabled",
19071907
"description": "Whether to automatically time web client requests.",
1908-
"defaultValue": true
1908+
"defaultValue": true,
1909+
"deprecation": {
1910+
"level": "error",
1911+
"reason": "Should be applied at the ObservationRegistry level."
1912+
}
19091913
},
19101914
{
19111915
"name": "management.metrics.web.client.request.autotime.percentiles",
1912-
"description": "Computed non-aggregable percentiles to publish."
1916+
"description": "Computed non-aggregable percentiles to publish.",
1917+
"deprecation": {
1918+
"level": "error",
1919+
"reason": "Should be applied at the ObservationRegistry level."
1920+
}
19131921
},
19141922
{
19151923
"name": "management.metrics.web.client.request.autotime.percentiles-histogram",
19161924
"description": "Whether percentile histograms should be published.",
1917-
"defaultValue": false
1925+
"defaultValue": false,
1926+
"deprecation": {
1927+
"level": "error",
1928+
"reason": "Should be applied at the ObservationRegistry level."
1929+
}
19181930
},
19191931
{
19201932
"name": "management.metrics.web.client.requests-metric-name",

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetri
7777
org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration
7878
org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsListenerAutoConfiguration
7979
org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration
80-
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration
80+
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientObservationsAutoConfiguration
8181
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration
8282
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration
8383
org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration;
4242
import org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration;
4343
import org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration;
44-
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration;
44+
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientObservationsAutoConfiguration;
4545
import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration;
4646
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration;
4747
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
@@ -140,7 +140,7 @@ void metricsFilterRegisteredForAsyncDispatches() {
140140
JvmMetricsAutoConfiguration.class, LogbackMetricsAutoConfiguration.class,
141141
SystemMetricsAutoConfiguration.class, RabbitMetricsAutoConfiguration.class,
142142
CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class,
143-
HibernateMetricsAutoConfiguration.class, HttpClientMetricsAutoConfiguration.class,
143+
HibernateMetricsAutoConfiguration.class, HttpClientObservationsAutoConfiguration.class,
144144
WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
145145
HttpMessageConvertersAutoConfiguration.class, RestTemplateAutoConfiguration.class,
146146
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,

0 commit comments

Comments
 (0)