Skip to content

Commit

Permalink
Auto-configure Observation support for RestTemplate
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bclozel committed Sep 27, 2022
1 parent 3acd9b8 commit eac50a8
Show file tree
Hide file tree
Showing 24 changed files with 621 additions and 677 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ dependencies {

testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("io.micrometer:micrometer-observation-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("io.r2dbc:r2dbc-h2")
testImplementation("com.squareup.okhttp3:mockwebserver")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

/**
Expand Down Expand Up @@ -158,6 +159,7 @@ public AutoTimeProperties getAutotime() {
return this.autotime;
}

@DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name")
public String getMetricName() {
return this.metricName;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2012-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;

import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
import org.springframework.http.client.observation.ClientHttpObservationContext;
import org.springframework.http.client.observation.ClientHttpObservationConvention;

/**
* Adapter class that applies {@link RestTemplateExchangeTagsProvider} tags as a
* {@link ClientHttpObservationConvention}.
*
* @author Brian Clozel
*/
@SuppressWarnings("deprecation")
class ClientHttpObservationConventionAdapter implements ClientHttpObservationConvention {

private final String metricName;

private final RestTemplateExchangeTagsProvider tagsProvider;

ClientHttpObservationConventionAdapter(String metricName, RestTemplateExchangeTagsProvider tagsProvider) {
this.metricName = metricName;
this.tagsProvider = tagsProvider;
}

@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof ClientHttpObservationContext;
}

@Override
public KeyValues getLowCardinalityKeyValues(ClientHttpObservationContext context) {
KeyValues keyValues = KeyValues.empty();
Iterable<Tag> tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(),
context.getResponse());
for (Tag tag : tags) {
keyValues = keyValues.and(tag.getKey(), tag.getValue());
}
return keyValues;
}

@Override
public KeyValues getHighCardinalityKeyValues(ClientHttpObservationContext context) {
return KeyValues.empty();
}

@Override
public String getName() {
return this.metricName;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
import io.micrometer.core.instrument.config.MeterFilter;

import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
Expand All @@ -42,11 +42,11 @@
* @author Raheela Aslam
* @since 2.1.0
*/
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class, RestTemplateAutoConfiguration.class })
@AutoConfiguration(after = { ObservationAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
RestTemplateAutoConfiguration.class, WebClientAutoConfiguration.class })
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
@Import({ RestTemplateMetricsConfiguration.class, WebClientMetricsConfiguration.class })
@Import({ RestTemplateObservationConfiguration.class, WebClientMetricsConfiguration.class })
public class HttpClientMetricsAutoConfiguration {

@Bean
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2012-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer;
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.observation.ClientHttpObservationConvention;
import org.springframework.http.client.observation.DefaultClientHttpObservationConvention;
import org.springframework.web.client.RestTemplate;

/**
* Configure the instrumentation of {@link RestTemplate}.
*
* @author Brian Clozel
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RestTemplate.class, Observation.class })
@ConditionalOnBean({ RestTemplateBuilder.class, ObservationRegistry.class })
@SuppressWarnings("deprecation")
class RestTemplateObservationConfiguration {

@Bean
ObservationRestTemplateCustomizer metricsRestTemplateCustomizer(ObservationRegistry observationRegistry,
ObservationProperties observationProperties, MetricsProperties metricsProperties,
ObjectProvider<RestTemplateExchangeTagsProvider> optionalTagsProvider) {
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
String name = (observationName != null) ? observationName : metricName;
RestTemplateExchangeTagsProvider tagsProvider = optionalTagsProvider.getIfAvailable();
ClientHttpObservationConvention observationConvention = (tagsProvider != null)
? new ClientHttpObservationConventionAdapter(name, tagsProvider)
: new DefaultClientHttpObservationConvention(name);
return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
*
* @author Moritz Halbritter
* @author Brian Clozel
* @since 3.0.0
*/
@AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
@ConditionalOnClass(ObservationRegistry.class)
@EnableConfigurationProperties(ObservationProperties.class)
public class ObservationAutoConfiguration {

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2012-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* {@link ConfigurationProperties @ConfigurationProperties} for configuring Micrometer
* observations.
*
* @author Brian Clozel
* @since 3.0.0
*/
@ConfigurationProperties("management.observations")
public class ObservationProperties {

private final Http http = new Http();

public Http getHttp() {
return this.http;
}

public static class Http {

private final Client client = new Client();

public Client getClient() {
return this.client;
}

public static class Client {

private final ClientRequests requests = new ClientRequests();

public ClientRequests getRequests() {
return this.requests;
}

public static class ClientRequests {

/**
* Name of the observation for client requests. If empty, will use the
* default "http.client.requests".
*/
private String name;

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

}

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
Expand Down Expand Up @@ -135,12 +136,12 @@ void metricsFilterRegisteredForAsyncDispatches() {
}

@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration({ MetricsAutoConfiguration.class, JvmMetricsAutoConfiguration.class,
LogbackMetricsAutoConfiguration.class, SystemMetricsAutoConfiguration.class,
RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class,
DataSourcePoolMetricsAutoConfiguration.class, HibernateMetricsAutoConfiguration.class,
HttpClientMetricsAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class,
WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
@ImportAutoConfiguration({ MetricsAutoConfiguration.class, ObservationAutoConfiguration.class,
JvmMetricsAutoConfiguration.class, LogbackMetricsAutoConfiguration.class,
SystemMetricsAutoConfiguration.class, RabbitMetricsAutoConfiguration.class,
CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class,
HibernateMetricsAutoConfiguration.class, HttpClientMetricsAutoConfiguration.class,
WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, RestTemplateAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class })
Expand Down
Loading

0 comments on commit eac50a8

Please sign in to comment.