Skip to content

Commit

Permalink
Micrometer (#1188)
Browse files Browse the repository at this point in the history
* Add support for micrometrics metrics

* Update documentation with metrics information
  • Loading branch information
velo committed Mar 15, 2020
1 parent 7b3acf2 commit 6989b50
Show file tree
Hide file tree
Showing 15 changed files with 796 additions and 39 deletions.
101 changes: 68 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ This is a map with current key features provided by feign:
![MindMap overview](src/docs/overview.png)

# Roadmap
## Feign 11 and beyond
## Feign 11 and beyond
Making _API_ clients easier

Short Term - What we're working on now. ⏰
Short Term - What we're working on now. ⏰
---
* Response Caching
* Support caching of api responses. Allow for users to define under what conditions a response is eligible for caching and what type of caching mechanism should be used.
Expand All @@ -42,22 +42,20 @@ Short Term - What we're working on now. ⏰
* `Retry` API refactor
* Refactor the `Retry` API to support user-supplied conditions and better control over back-off policies. **This may result in non-backward-compatible breaking changes**

Medium Term - What's up next. ⏲
Medium Term - What's up next. ⏲
---
* Metric API
* Provide a first-class Metrics API that users can tap into to gain insight into the request/response lifecycle. Possibly provide better [OpenTracing](https://opentracing.io/) support.
* Async execution support via `CompletableFuture`
* Allow for `Future` chaining and executor management for the request/response lifecycle. **Implementation will require non-backward-compatible breaking changes**. However this feature is required before Reactive execution can be considered.
* Reactive execution support via [Reactive Streams](https://www.reactive-streams.org/)
* For JDK 9+, consider a native implementation that uses `java.util.concurrent.Flow`.
* Support for [Project Reactor](https://projectreactor.io/) and [RxJava 2+](https://github.com/ReactiveX/RxJava) implementations on JDK 8.

Long Term - The future ☁️
Long Term - The future ☁️
---
* Additional Circuit Breaker Support.
* Support additional Circuit Breaker implementations like [Resilience4J](https://resilience4j.readme.io/) and Spring Circuit Breaker

---
---

### Basics

Expand Down Expand Up @@ -91,7 +89,7 @@ public class MyApp {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");

// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
Expand Down Expand Up @@ -125,22 +123,22 @@ should work. Feign's default contract defines the following annotations:
> ```java
> @RequestLine("POST /repos/{owner}/{repo}/issues")
> void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
> ```
>
> ```
>
### Templates and Expressions

Feign `Expressions` represent Simple String Expressions (Level 1) as defined by [URI Template - RFC 6570](https://tools.ietf.org/html/rfc6570). `Expressions` are expanded using
their corresponding `Param` annotated method parameters.
their corresponding `Param` annotated method parameters.

*Example*

```java
public interface GitHub {

@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);

class Contributor {
String login;
int contributions;
Expand All @@ -152,10 +150,10 @@ public class MyApp {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");

/* The owner and repository parameters will be used to expand the owner and repo expressions
* defined in the RequestLine.
*
*
* the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
*/
github.contributors("OpenFeign", "feign");
Expand Down Expand Up @@ -222,8 +220,8 @@ http://localhost:8080/test
See [Advanced Usage](#advanced-usage) for more examples.

> **What about slashes? `/`**
>
> @RequestLine templates do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` property on the `@RequestLine` to `false`.
>
> @RequestLine templates do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` property on the `@RequestLine` to `false`.
> **What about plus? `+`**
>
Expand All @@ -232,7 +230,7 @@ See [Advanced Usage](#advanced-usage) for more examples.
> `+` symbol should not represent a space and is explicitly encoded as `%2B` when found on a query string.
>
> If you wish to use `+` as a space, then use the literal ` ` character or encode the value directly as `%20`
##### Custom Expansion

The `@Param` annotation has an optional property `expander` allowing for complete control over the individual parameter's expansion.
Expand All @@ -246,17 +244,17 @@ public interface Expander {
The result of this method adheres to the same rules stated above. If the result is `null` or an empty string,
the value is omitted. If the value is not pct-encoded, it will be. See [Custom @Param Expansion](#custom-param-expansion) for more examples.

#### Request Headers Expansion
#### Request Headers Expansion

`Headers` and `HeaderMap` templates follow the same rules as [Request Parameter Expansion](#request-parameter-expansion)
`Headers` and `HeaderMap` templates follow the same rules as [Request Parameter Expansion](#request-parameter-expansion)
with the following alterations:

* Unresolved expressions are omitted. If the result is an empty header value, the entire header is removed.
* No pct-encoding is performed.

See [Headers](#headers) for examples.

> **A Note on `@Param` parameters and their names**:
> **A Note on `@Param` parameters and their names**:
>
> All expressions with the same name, regardless of their position on the `@RequestLine`, `@QueryMap`, `@BodyTemplate`, or `@Headers` will resolve to the same value.
> In the following example, the value of `contentType`, will be used to resolve both the header and path expression:
Expand All @@ -268,12 +266,12 @@ See [Headers](#headers) for examples.
> String getDocumentByType(@Param("contentType") String type);
> }
>```
>
>
> Keep this in mind when designing your interfaces.
#### Request Body Expansion

`Body` templates follow the same rules as [Request Parameter Expansion](#request-parameter-expansion)
`Body` templates follow the same rules as [Request Parameter Expansion](#request-parameter-expansion)
with the following alterations:

* Unresolved expressions are omitted.
Expand Down Expand Up @@ -311,7 +309,7 @@ public class CloudService {
CloudDNS cloudDNS = Feign.builder()
.target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
}

class CloudIdentityTarget extends Target<CloudDNS> {
/* implementation of a Target */
}
Expand Down Expand Up @@ -571,7 +569,7 @@ public class Example {
LoginClient client = Feign.builder()
.encoder(new GsonEncoder())
.target(LoginClient.class, "https://foo.com");

client.login(new Credentials("denominator", "secret"));
}
}
Expand Down Expand Up @@ -648,11 +646,11 @@ These approaches specify header entries as part of the api and do not require an
when building the Feign client.

#### Setting headers per target
To customize headers for each request method on a Target, a RequestInterceptor can be used. RequestInterceptors can be
shared across Target instances and are expected to be thread-safe. RequestInterceptors are applied to all request
To customize headers for each request method on a Target, a RequestInterceptor can be used. RequestInterceptors can be
shared across Target instances and are expected to be thread-safe. RequestInterceptors are applied to all request
methods on a Target.

If you need per method customization, a custom Target is required, as the a RequestInterceptor does not have access to
If you need per method customization, a custom Target is required, as the a RequestInterceptor does not have access to
the current method metadata.

For an example of setting headers using a `RequestInterceptor`, see the `Request Interceptors` section.
Expand All @@ -664,7 +662,7 @@ Headers can be set as part of a custom `Target`.
public DynamicAuthTokenTarget(Class<T> clazz,
UrlAndTokenProvider provider,
ThreadLocal<String> requestIdProvider);

@Override
public Request apply(RequestTemplate input) {
TokenIdAndPublicURL urlAndToken = provider.get();
Expand All @@ -677,7 +675,7 @@ Headers can be set as part of a custom `Target`.
return input.request();
}
}

public class Example {
public static void main(String[] args) {
Bank bank = Feign.builder()
Expand Down Expand Up @@ -755,7 +753,7 @@ public class Example {
}
```

> **A Note on JavaLogger**:
> **A Note on JavaLogger**:
> Avoid using of default ```JavaLogger()``` constructor - it was marked as deprecated and will be removed soon.
The SLF4JLogger (see above) may also be of interest.
Expand Down Expand Up @@ -898,12 +896,49 @@ public class Example {
```

`Retryer`s are responsible for determining if a retry should occur by returning either a `true` or
`false` from the method `continueOrPropagate(RetryableException e);` A `Retryer` instance will be
`false` from the method `continueOrPropagate(RetryableException e);` A `Retryer` instance will be
created for each `Client` execution, allowing you to maintain state bewteen each request if desired.

If the retry is determined to be unsuccessful, the last `RetryException` will be thrown. To throw the original
cause that led to the unsuccessful retry, build your Feign client with the `exceptionPropagationPolicy()` option.

### Metrics
By default, feign won't collect any metrics.

But, it's possible to add metric collection capabilities to any feign client.

Metric Capabilities provide a first-class Metrics API that users can tap into to gain insight into the request/response lifecycle.

#### Dropwizard Metrics 5

```
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new Metrics5Capability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
```

#### Micrometer

```
public class MyApp {
public static void main(String[] args) {
GitHub github = Feign.builder()
.addCapability(new MicrometerCapability())
.target(GitHub.class, "https://api.github.com");
github.contributors("OpenFeign", "feign");
// metrics will be available from this point onwards
}
}
```

#### Static and Default Methods
Interfaces targeted by Feign may have static or default methods (if using Java 8+).
These allows Feign clients to contain logic that is not expressly defined by the underlying API.
Expand Down Expand Up @@ -956,7 +991,7 @@ public class MyApp {
GitHub github = AsyncFeign.asyncBuilder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");

// Fetch and print a list of the contributors to this library.
CompletableFuture<List<Contributor>> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors.get(1, TimeUnit.SECONDS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,11 @@ public void addMetricsCapability() {
"Expect all metric names to include method name:" + metricName,
metricName.getTags(),
hasEntry("method", "get")));
registry.getMetrics().keySet().forEach(
metricName -> assertThat(
"Expect all metric names to include host name:" + metricName,
metricName.getTags(),
// hostname is null due to feign-mock shortfalls
hasEntry("host", null)));
registry.getMetrics().keySet().forEach(metricName -> assertThat(
"Expect all metric names to include host name:" + metricName,
metricName.getTags(),
// hostname is null due to feign-mock shortfalls
hasEntry("host", null)));
}

}
54 changes: 54 additions & 0 deletions micrometer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2012-2020 The Feign 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
http://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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.openfeign</groupId>
<artifactId>parent</artifactId>
<version>10.9-SNAPSHOT</version>
</parent>
<artifactId>feign-micrometer</artifactId>
<name>Feign Micrometer</name>
<description>Feign Micrometer Application Metrics</description>

<properties>
<main.basedir>${project.basedir}/..</main.basedir>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-mock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

0 comments on commit 6989b50

Please sign in to comment.