diff --git a/README.md b/README.md index 6e31ab89f..cbaae1076 100644 --- a/README.md +++ b/README.md @@ -1190,6 +1190,9 @@ public class MyApp { } ``` +See the [micrometer module README](./micrometer/README.md) for the full list of +metrics that are published, their tags and how to use `MicrometerObservationCapability`. + #### 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. diff --git a/micrometer/README.md b/micrometer/README.md new file mode 100644 index 000000000..d060cefbe --- /dev/null +++ b/micrometer/README.md @@ -0,0 +1,127 @@ +Micrometer +=================== + +This module integrates Feign with [Micrometer](https://micrometer.io/) so that +HTTP calls made through Feign clients are observable through any Micrometer-supported +monitoring system (Prometheus, Datadog, CloudWatch, etc.). + +Two capabilities are provided: + +* `MicrometerCapability` — publishes timers, counters and distribution summaries + to a `MeterRegistry`. +* `MicrometerObservationCapability` — publishes Micrometer `Observation`s to an + `ObservationRegistry` (which can in turn produce metrics and tracing spans). + +Pick one — both record the same underlying call, so registering both would +double-count. + +## Usage + +### `MicrometerCapability` + +```java +GitHub github = Feign.builder() + .addCapability(new MicrometerCapability()) + .target(GitHub.class, "https://api.github.com"); +``` + +By default, metrics are registered with `io.micrometer.core.instrument.Metrics.globalRegistry`. +Pass your own registry and/or common tags if you want explicit control: + +```java +MeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); + +GitHub github = Feign.builder() + .addCapability(new MicrometerCapability(registry, Tags.of("application", "my-app"))) + .target(GitHub.class, "https://api.github.com"); +``` + +### `MicrometerObservationCapability` + +```java +ObservationRegistry observationRegistry = ObservationRegistry.create(); + +GitHub github = Feign.builder() + .addCapability(new MicrometerObservationCapability(observationRegistry)) + .target(GitHub.class, "https://api.github.com"); +``` + +To customize the tags or name of the observation, implement +`FeignObservationConvention` (or extend `DefaultFeignObservationConvention`) and +pass it to the constructor: + +```java +new MicrometerObservationCapability(observationRegistry, new MyFeignObservationConvention()); +``` + +## Metrics published by `MicrometerCapability` + +The capability instruments five stages of a Feign call. Each stage emits a +timer; counters and distribution summaries are emitted where noted. + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `feign.Feign` | Timer | Total time spent in the Feign method invocation, including encoding, the HTTP call and decoding. Recorded by `MeteredInvocationHandleFactory`. | +| `feign.Feign.exception` | Timer | Same as above, recorded when the invocation throws (any `Throwable`). | +| `feign.Feign.http_error` | Counter | Incremented once per `FeignException` thrown by an invocation. Adds `http_status` and `error_group` tags. | +| `feign.Client` | Timer | Time spent in the underlying HTTP client for a single request. Recorded by `MeteredClient`. | +| `feign.Client.exception` | Timer | Same as above, recorded when the HTTP call throws. | +| `feign.Client.http_response_code` | Counter | Incremented once per HTTP response (including error responses). Adds `http_status`, `status_group`, `http_method` and `uri` tags. | +| `feign.AsyncClient` | Timer | Async-equivalent of `feign.Client`. Recorded by `MeteredAsyncClient`. | +| `feign.AsyncClient.exception` | Timer | Async-equivalent of `feign.Client.exception`. | +| `feign.AsyncClient.http_response_code` | Counter | Async-equivalent of `feign.Client.http_response_code`. | +| `feign.codec.Encoder` | Timer | Time spent encoding the request body. Recorded by `MeteredEncoder`. | +| `feign.codec.Encoder.response_size` | DistributionSummary | Size, in bytes, of the encoded **request** body. The metric name predates the current behavior. | +| `feign.codec.Decoder` | Timer | Time spent decoding the response body. Recorded by `MeteredDecoder`. | +| `feign.codec.Decoder.exception` | Timer | Same as above, recorded when decoding throws. | +| `feign.codec.Decoder.response_size` | DistributionSummary | Size, in bytes, of the response body read by the decoder. | + +### Tags + +Every metric carries the following tags, populated by `FeignMetricTagResolver`: + +| Tag | Source | Example | +| --- | ------ | ------- | +| `client` | Target interface FQN | `com.example.GitHub` | +| `method` | Java method name | `contributors` | +| `host` | Host extracted from the target URL | `api.github.com` | +| `exception_name` | Exception simple name (only present on error) | `FeignException` | +| `root_cause_name` | Root cause exception simple name (only present on error) | `SocketTimeoutException` | + +Additional tags are added by specific metrics: + +* `feign.Client.http_response_code` / `feign.AsyncClient.http_response_code` — + adds `http_status` (e.g. `404`), `status_group` (e.g. `4xx`), `http_method` + (e.g. `GET`) and `uri` (the templated path, e.g. `/repos/{owner}/{repo}/contributors`). +* `feign.Feign.http_error` — adds `http_status` and `error_group` (e.g. `5xx`). +* `feign.codec.Decoder.*` — adds `uri`. + +You can attach extra common tags either through `MeterRegistry.config().commonTags(...)` +or via the `MicrometerCapability(MeterRegistry, List)` / +`MicrometerCapability(MeterRegistry, Map)` constructors. + +## Observation published by `MicrometerObservationCapability` + +A single observation is produced per HTTP call: + +| Name | Contextual name | Description | +| ---- | --------------- | ----------- | +| `http.client.requests` | `HTTP {method}` (e.g. `HTTP GET`) | One observation per HTTP request issued by the Feign client. The observation is stopped after the response is received or an exception is signalled. | + +The default convention (`DefaultFeignObservationConvention`) attaches the +following low-cardinality key values: + +| Key | Value | +| --- | ----- | +| `http.method` | The HTTP method (`GET`, `POST`, ...). `UNKNOWN` if the request is null. | +| `http.url` | The templated URL of the method. | +| `http.status_code` | The response status code, or `CLIENT_ERROR` if no response was received. | +| `clientName` | The target interface FQN. | + +The following key names are also declared on `FeignObservationDocumentation` and +are available for custom conventions to populate: `http.scheme`, `net.peer.host`, +`net.peer.port`. + +Whatever observation handlers are registered on the `ObservationRegistry` +(for example `DefaultMeterObservationHandler` for metrics, a tracing handler +for spans) decide what is ultimately emitted.