Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Micrometer Observation API Support #793

Merged
merged 2 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
|spring.cloud.openfeign.httpclient.ok-http.read-timeout | `60s` | {@link OkHttpClient} read timeout; defaults to 60 seconds.
|spring.cloud.openfeign.httpclient.time-to-live | `900` |
|spring.cloud.openfeign.httpclient.time-to-live-unit | |
|spring.cloud.openfeign.metrics.enabled | `true` | Enables metrics capability for Feign.
|spring.cloud.openfeign.micrometer.enabled | `true` | Enables Micrometer capabilities for Feign.
|spring.cloud.openfeign.oauth2.enabled | `false` | Enables feign interceptor for managing oauth2 access token.
|spring.cloud.openfeign.oauth2.load-balanced | `false` | Enables load balancing for oauth2 access token provider.
|spring.cloud.openfeign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.

|===
|===
54 changes: 34 additions & 20 deletions docs/src/main/asciidoc/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`)
* `Encoder` feignEncoder: `SpringEncoder`
* `Logger` feignLogger: `Slf4jLogger`
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath and `MeterRegistry` is available
* `MicrometerObservationCapability` micrometerObservationCapability: If `feign-micrometer` is on the classpath and `ObservationRegistry` is available
* `MicrometerCapability` micrometerCapability: If `feign-micrometer` is on the classpath, `MeterRegistry` is available and `ObservationRegistry` is not available
* `CachingCapability` cachingCapability: If `@EnableCaching` annotation is used. Can be disabled via `spring.cloud.openfeign.cache.enabled`.
* `Contract` feignContract: `SpringMvcContract`
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder`
Expand All @@ -146,7 +147,7 @@ Spring Cloud OpenFeign _does not_ provide the following beans by default for fei
* `Collection<RequestInterceptor>`
* `SetterFactory`
* `QueryMapEncoder`
* `Capability` (`MicrometerCapability` and `CachingCapability` are provided by default)
* `Capability` (`MicrometerObservationCapability` and `CachingCapability` are provided by default)

A bean of `Retryer.NEVER_RETRY` with the type `Retryer` is created by default, which will disable retrying.
Notice this retrying behavior is different from the Feign default one, where it will automatically retry IOExceptions,
Expand Down Expand Up @@ -204,7 +205,7 @@ spring:
- com.example.FooCapability
- com.example.BarCapability
queryMapEncoder: com.example.SimpleQueryMapEncoder
metrics.enabled: false
micrometer.enabled: false
----

Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients.
Expand Down Expand Up @@ -311,20 +312,20 @@ class FooController {
private FooClient adminClient;

@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) {
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");

this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.addCapability(micrometerObservationCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
Expand Down Expand Up @@ -602,7 +603,7 @@ public class FooConfiguration {
=== Feign Capability support

The Feign capabilities expose core Feign components so that these components can be modified. For example, the capabilities can take the `Client`, _decorate_ it, and give the decorated instance back to Feign.
The support for metrics libraries is a good real-life example for this. See <<feign-metrics>>.
The support for Micrometer is a good real-life example for this. See <<micrometer-support>>.

Creating one or more `Capability` beans and placing them in a `@FeignClient` configuration lets you register them and modify the behavior of the involved client.

Expand All @@ -617,29 +618,42 @@ public class FooConfiguration {
}
----

=== Feign metrics
=== Micrometer Support

If all of the following conditions are true, a `MicrometerCapability` bean is created and registered so that your Feign client publishes metrics to Micrometer:
If all of the following conditions are true, a `MicrometerObservationCapability` bean is created and registered so that your Feign client is observable by Micrometer:

* `feign-micrometer` is on the classpath
* A `MeterRegistry` bean is available
* feign metrics properties are set to `true` (by default)
- `spring.cloud.openfeign.metrics.enabled=true` (for all clients)
- `spring.cloud.openfeign.client.config.feignName.metrics.enabled=true` (for a single client)
* A `ObservationRegistry` bean is available
* feign micrometer properties are set to `true` (by default)
- `spring.cloud.openfeign.micrometer.enabled=true` (for all clients)
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true` (for a single client)

NOTE: If your application already uses Micrometer, enabling metrics is as simple as putting `feign-micrometer` onto your classpath.
NOTE: If your application already uses Micrometer, enabling this feature is as simple as putting `feign-micrometer` onto your classpath.

You can also disable the feature by either:

* excluding `feign-micrometer` from your classpath
* setting one of the feign metrics properties to `false`
- `spring.cloud.openfeign.metrics.enabled=false`
- `spring.cloud.openfeign.client.config.feignName.metrics.enabled=false`
* setting one of the feign micrometer properties to `false`
- `spring.cloud.openfeign.micrometer.enabled=false`
- `spring.cloud.openfeign.client.config.feignName.micrometer.enabled=false`

NOTE: `spring.cloud.openfeign.metrics.enabled=false` disables metrics support for *all* Feign clients regardless of the value of the client-level flags: `spring.cloud.openfeign.client.config.feignName.metrics.enabled`.
If you want to enable or disable merics per client, don't set `spring.cloud.openfeign.metrics.enabled` and use `spring.cloud.openfeign.client.config.feignName.metrics.enabled`.
NOTE: `spring.cloud.openfeign.micrometer.enabled=false` disables Micrometer support for *all* Feign clients regardless of the value of the client-level flags: `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`.
If you want to enable or disable Micrometer support per client, don't set `spring.cloud.openfeign.micrometer.enabled` and use `spring.cloud.openfeign.client.config.feignName.micrometer.enabled`.

You can also customize the `MicrometerCapability` by registering your own bean:
You can also customize the `MicrometerObservationCapability` by registering your own bean:

[source,java,indent=0]
----
@Configuration
public class FooConfiguration {
@Bean
public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
return new MicrometerObservationCapability(registry);
}
}
----

It is still possible to use `MicrometerCapability` with Feign (metrics-only support), you need to disable Micrometer support (`spring.cloud.openfeign.micrometer.enabled=false`) and create a `MicrometerCapability` bean:

[source,java,indent=0]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/**
* @author Jonatan Ivanov
*/
class FeignClientMetricsEnabledCondition implements Condition {
class FeignClientMicrometerEnabledCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Expand All @@ -38,9 +38,9 @@ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
FeignClientProperties.FeignClientConfiguration feignClientConfig = feignClientConfigMap
.get(context.getEnvironment().getProperty("spring.cloud.openfeign.client.name"));
if (feignClientConfig != null) {
FeignClientProperties.MetricsProperties metrics = feignClientConfig.getMetrics();
if (metrics != null && metrics.getEnabled() != null) {
return metrics.getEnabled();
FeignClientProperties.MicrometerProperties micrometer = feignClientConfig.getMicrometer();
if (micrometer != null && micrometer.getEnabled() != null) {
return micrometer.getEnabled();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public static class FeignClientConfiguration {

private Class<QueryMapEncoder> queryMapEncoder;

private MetricsProperties metrics;
private MicrometerProperties micrometer;

private Boolean followRedirects;

Expand Down Expand Up @@ -274,12 +274,12 @@ public void setQueryMapEncoder(Class<QueryMapEncoder> queryMapEncoder) {
this.queryMapEncoder = queryMapEncoder;
}

public MetricsProperties getMetrics() {
return metrics;
public MicrometerProperties getMicrometer() {
return micrometer;
}

public void setMetrics(MetricsProperties metrics) {
this.metrics = metrics;
public void setMicrometer(MicrometerProperties micrometer) {
this.micrometer = micrometer;
}

public Boolean isFollowRedirects() {
Expand Down Expand Up @@ -317,23 +317,24 @@ public boolean equals(Object o) {
&& Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders)
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters)
&& Objects.equals(capabilities, that.capabilities)
&& Objects.equals(queryMapEncoder, that.queryMapEncoder) && Objects.equals(metrics, that.metrics)
&& Objects.equals(queryMapEncoder, that.queryMapEncoder)
&& Objects.equals(micrometer, that.micrometer)
&& Objects.equals(followRedirects, that.followRedirects) && Objects.equals(url, that.url);
}

@Override
public int hashCode() {
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
dismiss404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
defaultRequestHeaders, capabilities, queryMapEncoder, metrics, followRedirects, url);
defaultRequestHeaders, capabilities, queryMapEncoder, micrometer, followRedirects, url);
}

}

/**
* Metrics configuration for Feign Client.
* Micrometer configuration for Feign Client.
*/
public static class MetricsProperties {
public static class MicrometerProperties {

private Boolean enabled = true;

Expand All @@ -354,7 +355,7 @@ public boolean equals(Object o) {
return false;
}

MetricsProperties that = (MetricsProperties) o;
MicrometerProperties that = (MicrometerProperties) o;
return Objects.equals(enabled, that.enabled);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringFormEncoder;
import feign.micrometer.MicrometerCapability;
import feign.micrometer.MicrometerObservationCapability;
import feign.optionals.OptionalDecoder;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.ObservationRegistry;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
Expand Down Expand Up @@ -235,16 +237,24 @@ public Feign.Builder circuitBreakerFeignBuilder() {
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry")
@ConditionalOnClass(name = "feign.micrometer.MicrometerCapability")
@ConditionalOnProperty(name = "spring.cloud.openfeign.metrics.enabled", matchIfMissing = true)
@Conditional(FeignClientMetricsEnabledCondition.class)
protected static class MetricsConfiguration {
@ConditionalOnProperty(name = "spring.cloud.openfeign.micrometer.enabled", matchIfMissing = true)
@Conditional(FeignClientMicrometerEnabledCondition.class)
protected static class MicrometerConfiguration {

@Bean
@ConditionalOnMissingBean
public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) {
return new MicrometerCapability(meterRegistry);
@ConditionalOnClass(name = "feign.micrometer.MicrometerObservationCapability")
@ConditionalOnBean(type = "io.micrometer.observation.ObservationRegistry")
public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
return new MicrometerObservationCapability(registry);
}

@Bean
@ConditionalOnClass(name = "feign.micrometer.MicrometerCapability")
@ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry")
@ConditionalOnMissingBean({ MicrometerCapability.class, MicrometerObservationCapability.class })
public MicrometerCapability micrometerCapability(MeterRegistry registry) {
return new MicrometerCapability(registry);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
"defaultValue": "false"
},
{
"name": "spring.cloud.openfeign.metrics.enabled",
"name": "spring.cloud.openfeign.micrometer.enabled",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the property name need to change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes. I don't think metrics.enabled makes any sense when the users don't even have metrics, e.g.: they enable tracing but not metrics through the Observation API.
Or to put it in a different way: SC-OpenFeign has no idea what the user will configure in their ObservationRegistry.

"type": "java.lang.Boolean",
"description": "Enables metrics capability for Feign.",
"description": "Enables Micrometer capabilities for Feign.",
"defaultValue": "true"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void shouldDefaultToValuesWhenFieldsNotSet() {
assertThat(config.getExceptionPropagationPolicy()).isNull();
assertThat(config.getCapabilities()).isNull();
assertThat(config.getQueryMapEncoder()).isNull();
assertThat(config.getMetrics()).isNull();
assertThat(config.getMicrometer()).isNull();
}

@Test
Expand All @@ -94,8 +94,8 @@ void shouldReturnValuesWhenSet() {
List<Class<Capability>> capabilities = Lists.list(Capability.class);
config.setCapabilities(capabilities);
config.setQueryMapEncoder(QueryMapEncoder.class);
FeignClientProperties.MetricsProperties metrics = new FeignClientProperties.MetricsProperties();
config.setMetrics(metrics);
FeignClientProperties.MicrometerProperties micrometer = new FeignClientProperties.MicrometerProperties();
config.setMicrometer(micrometer);

assertThat(config.getLoggerLevel()).isSameAs(Logger.Level.FULL);
assertThat(config.getConnectTimeout()).isEqualTo(21);
Expand All @@ -112,7 +112,7 @@ void shouldReturnValuesWhenSet() {
assertThat(config.getExceptionPropagationPolicy()).isSameAs(ExceptionPropagationPolicy.UNWRAP);
assertThat(config.getCapabilities()).isSameAs(capabilities);
assertThat(config.getQueryMapEncoder()).isSameAs(QueryMapEncoder.class);
assertThat(config.getMetrics()).isSameAs(metrics);
assertThat(config.getMicrometer()).isSameAs(micrometer);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import feign.Contract;
import feign.RequestLine;
import feign.micrometer.MicrometerCapability;
import feign.micrometer.MicrometerObservationCapability;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -40,7 +41,7 @@
* @author Jonatan Ivanov
*/
@DirtiesContext
@ActiveProfiles("no-foo-metrics")
@ActiveProfiles("no-foo-micrometer")
@SpringBootTest(classes = FeignClientDisabledClientLevelFeaturesTests.TestConfiguration.class)
class FeignClientDisabledClientLevelFeaturesTests {

Expand All @@ -62,12 +63,15 @@ void clientsAvailable() {
@Test
void capabilitiesShouldNotBeAvailableWhenDisabled() {
assertThat(context.getInstance("foo", MicrometerCapability.class)).isNull();
assertThat(context.getInstance("foo", MicrometerObservationCapability.class)).isNull();
assertThat(context.getInstances("foo", Capability.class)).isEmpty();

assertThat(context.getInstance("bar", MicrometerCapability.class)).isNotNull();
assertThat(context.getInstance("bar", MicrometerCapability.class)).isNull();
assertThat(context.getInstance("bar", MicrometerObservationCapability.class)).isNotNull();
Map<String, Capability> barCapabilities = context.getInstances("bar", Capability.class);
assertThat(barCapabilities).hasSize(2);
assertThat(barCapabilities.get("micrometerCapability")).isExactlyInstanceOf(MicrometerCapability.class);
assertThat(barCapabilities.get("micrometerObservationCapability"))
.isExactlyInstanceOf(MicrometerObservationCapability.class);
assertThat(barCapabilities.get("noOpCapability")).isExactlyInstanceOf(NoOpCapability.class);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* @author Jonatan Ivanov
*/
@DirtiesContext
@ActiveProfiles("no-metrics")
@ActiveProfiles("no-micrometer")
@SpringBootTest(classes = FeignClientDisabledFeaturesTests.TestConfiguration.class)
class FeignClientDisabledFeaturesTests {

Expand Down
Loading