Skip to content

Commit eac50a8

Browse files
committed
Auto-configure Observation support for RestTemplate
Prior to this commit, Spring Boot would auto-configure a customizer that instruments `RestTemplate` through a `RestTemplateBuilder`. This would install a request interceptor that instrumented client exchanges for producing metrics. As of spring-projects/spring-framework#28341, the instrumentation is done at the `RestTemplate` level directly using the `Observation` API. The `Tag` (now `KeyValue`) extraction, observation name and instrumentation behavior now lives in the Spring Framework project. This commit updates the auto-configuration to switch from Boot-specific Metrics instrumentation to a generic Observation instrumentation. As a migration path, some configuration properties are deprecated in favor of the new `management.observations.*` namespace. Closes gh-32484
1 parent 3acd9b8 commit eac50a8

24 files changed

+621
-677
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ dependencies {
147147

148148
testImplementation(project(":spring-boot-project:spring-boot-test"))
149149
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
150+
testImplementation("io.micrometer:micrometer-observation-test")
150151
testImplementation("io.projectreactor:reactor-test")
151152
testImplementation("io.r2dbc:r2dbc-h2")
152153
testImplementation("com.squareup.okhttp3:mockwebserver")

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

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Map;
2626

2727
import org.springframework.boot.context.properties.ConfigurationProperties;
28+
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
2829
import org.springframework.boot.context.properties.NestedConfigurationProperty;
2930

3031
/**
@@ -158,6 +159,7 @@ public AutoTimeProperties getAutotime() {
158159
return this.autotime;
159160
}
160161

162+
@DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name")
161163
public String getMetricName() {
162164
return this.metricName;
163165
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.client.RestTemplateExchangeTagsProvider;
24+
import org.springframework.http.client.observation.ClientHttpObservationContext;
25+
import org.springframework.http.client.observation.ClientHttpObservationConvention;
26+
27+
/**
28+
* Adapter class that applies {@link RestTemplateExchangeTagsProvider} tags as a
29+
* {@link ClientHttpObservationConvention}.
30+
*
31+
* @author Brian Clozel
32+
*/
33+
@SuppressWarnings("deprecation")
34+
class ClientHttpObservationConventionAdapter implements ClientHttpObservationConvention {
35+
36+
private final String metricName;
37+
38+
private final RestTemplateExchangeTagsProvider tagsProvider;
39+
40+
ClientHttpObservationConventionAdapter(String metricName, RestTemplateExchangeTagsProvider tagsProvider) {
41+
this.metricName = metricName;
42+
this.tagsProvider = tagsProvider;
43+
}
44+
45+
@Override
46+
public boolean supportsContext(Observation.Context context) {
47+
return context instanceof ClientHttpObservationContext;
48+
}
49+
50+
@Override
51+
public KeyValues getLowCardinalityKeyValues(ClientHttpObservationContext context) {
52+
KeyValues keyValues = KeyValues.empty();
53+
Iterable<Tag> tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(),
54+
context.getResponse());
55+
for (Tag tag : tags) {
56+
keyValues = keyValues.and(tag.getKey(), tag.getValue());
57+
}
58+
return keyValues;
59+
}
60+
61+
@Override
62+
public KeyValues getHighCardinalityKeyValues(ClientHttpObservationContext context) {
63+
return KeyValues.empty();
64+
}
65+
66+
@Override
67+
public String getName() {
68+
return this.metricName;
69+
}
70+
71+
}

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
import io.micrometer.core.instrument.config.MeterFilter;
2121

2222
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
23-
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
2423
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
2524
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
26-
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
2726
import org.springframework.boot.autoconfigure.AutoConfiguration;
2827
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2928
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3029
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3130
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
3232
import org.springframework.context.annotation.Bean;
3333
import org.springframework.context.annotation.Import;
3434
import org.springframework.core.annotation.Order;
@@ -42,11 +42,11 @@
4242
* @author Raheela Aslam
4343
* @since 2.1.0
4444
*/
45-
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
46-
SimpleMetricsExportAutoConfiguration.class, RestTemplateAutoConfiguration.class })
45+
@AutoConfiguration(after = { ObservationAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
46+
RestTemplateAutoConfiguration.class, WebClientAutoConfiguration.class })
4747
@ConditionalOnClass(MeterRegistry.class)
4848
@ConditionalOnBean(MeterRegistry.class)
49-
@Import({ RestTemplateMetricsConfiguration.class, WebClientMetricsConfiguration.class })
49+
@Import({ RestTemplateObservationConfiguration.class, WebClientMetricsConfiguration.class })
5050
public class HttpClientMetricsAutoConfiguration {
5151

5252
@Bean

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

-60
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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.observation.Observation;
20+
import io.micrometer.observation.ObservationRegistry;
21+
22+
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
24+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
25+
import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer;
26+
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
29+
import org.springframework.boot.web.client.RestTemplateBuilder;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.http.client.observation.ClientHttpObservationConvention;
33+
import org.springframework.http.client.observation.DefaultClientHttpObservationConvention;
34+
import org.springframework.web.client.RestTemplate;
35+
36+
/**
37+
* Configure the instrumentation of {@link RestTemplate}.
38+
*
39+
* @author Brian Clozel
40+
*/
41+
@Configuration(proxyBeanMethods = false)
42+
@ConditionalOnClass({ RestTemplate.class, Observation.class })
43+
@ConditionalOnBean({ RestTemplateBuilder.class, ObservationRegistry.class })
44+
@SuppressWarnings("deprecation")
45+
class RestTemplateObservationConfiguration {
46+
47+
@Bean
48+
ObservationRestTemplateCustomizer metricsRestTemplateCustomizer(ObservationRegistry observationRegistry,
49+
ObservationProperties observationProperties, MetricsProperties metricsProperties,
50+
ObjectProvider<RestTemplateExchangeTagsProvider> optionalTagsProvider) {
51+
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
52+
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
53+
String name = (observationName != null) ? observationName : metricName;
54+
RestTemplateExchangeTagsProvider tagsProvider = optionalTagsProvider.getIfAvailable();
55+
ClientHttpObservationConvention observationConvention = (tagsProvider != null)
56+
? new ClientHttpObservationConventionAdapter(name, tagsProvider)
57+
: new DefaultClientHttpObservationConvention(name);
58+
return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
59+
}
60+
61+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/observation/ObservationAutoConfiguration.java

+3
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,20 @@
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3434
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3535
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
36+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3637
import org.springframework.context.annotation.Bean;
3738
import org.springframework.context.annotation.Configuration;
3839

3940
/**
4041
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
4142
*
4243
* @author Moritz Halbritter
44+
* @author Brian Clozel
4345
* @since 3.0.0
4446
*/
4547
@AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
4648
@ConditionalOnClass(ObservationRegistry.class)
49+
@EnableConfigurationProperties(ObservationProperties.class)
4750
public class ObservationAutoConfiguration {
4851

4952
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.observation;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* {@link ConfigurationProperties @ConfigurationProperties} for configuring Micrometer
23+
* observations.
24+
*
25+
* @author Brian Clozel
26+
* @since 3.0.0
27+
*/
28+
@ConfigurationProperties("management.observations")
29+
public class ObservationProperties {
30+
31+
private final Http http = new Http();
32+
33+
public Http getHttp() {
34+
return this.http;
35+
}
36+
37+
public static class Http {
38+
39+
private final Client client = new Client();
40+
41+
public Client getClient() {
42+
return this.client;
43+
}
44+
45+
public static class Client {
46+
47+
private final ClientRequests requests = new ClientRequests();
48+
49+
public ClientRequests getRequests() {
50+
return this.requests;
51+
}
52+
53+
public static class ClientRequests {
54+
55+
/**
56+
* Name of the observation for client requests. If empty, will use the
57+
* default "http.client.requests".
58+
*/
59+
private String name;
60+
61+
public String getName() {
62+
return this.name;
63+
}
64+
65+
public void setName(String name) {
66+
this.name = name;
67+
}
68+
69+
}
70+
71+
}
72+
73+
}
74+
75+
}

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration;
4545
import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration;
4646
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration;
47+
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
4748
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter;
4849
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
4950
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
@@ -135,12 +136,12 @@ void metricsFilterRegisteredForAsyncDispatches() {
135136
}
136137

137138
@Configuration(proxyBeanMethods = false)
138-
@ImportAutoConfiguration({ MetricsAutoConfiguration.class, JvmMetricsAutoConfiguration.class,
139-
LogbackMetricsAutoConfiguration.class, SystemMetricsAutoConfiguration.class,
140-
RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class,
141-
DataSourcePoolMetricsAutoConfiguration.class, HibernateMetricsAutoConfiguration.class,
142-
HttpClientMetricsAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class,
143-
WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
139+
@ImportAutoConfiguration({ MetricsAutoConfiguration.class, ObservationAutoConfiguration.class,
140+
JvmMetricsAutoConfiguration.class, LogbackMetricsAutoConfiguration.class,
141+
SystemMetricsAutoConfiguration.class, RabbitMetricsAutoConfiguration.class,
142+
CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class,
143+
HibernateMetricsAutoConfiguration.class, HttpClientMetricsAutoConfiguration.class,
144+
WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
144145
HttpMessageConvertersAutoConfiguration.class, RestTemplateAutoConfiguration.class,
145146
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
146147
ServletWebServerFactoryAutoConfiguration.class })

0 commit comments

Comments
 (0)