Apache HttpComponents Client Instrumentation

This section requires usage of Apache HttpComponents Client at least in version 5.

Apache HttpComponents Client is an HTTP/1.1 compliant HTTP agent implementation.

Below you can find an example of how to instrument Apache HttpComponents Client with Micrometer Observation. That means that depending on your Observation Handler configuration you instrument once, and can have multiple benefits out of it (e.g. metrics, distributed tracing).

Instrumentation

Example of classic, blocking HTTP client.

// Setting up instrumentation (you need to create a client from the builder)
HttpClientBuilder clientBuilder = HttpClients.custom()
    .setRetryStrategy(retryStrategy)
    .addExecInterceptorAfter(ChainElement.RETRY.name(), "micrometer",
            new ObservationExecChainHandler(observationRegistry))
    .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
        .setDefaultConnectionConfig(connectionConfig)
        .build());

// Usage example
try (CloseableHttpClient client = classicClient()) {
    executeClassic(client, new HttpGet(server.baseUrl()));
}
assertThat(observationRegistry).hasObservationWithNameEqualTo(DEFAULT_METER_NAME)
    .that()
    .hasLowCardinalityKeyValue(OUTCOME.withValue("SUCCESS"))
    .hasLowCardinalityKeyValue(STATUS.withValue("200"))
    .hasLowCardinalityKeyValue(METHOD.withValue("GET"));

Example of async HTTP client.

// Setting up instrumentation (you need to create a client from the builder)
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom()
    .addExecInterceptorAfter(ChainElement.RETRY.name(), "micrometer",
            new ObservationExecChainHandler(observationRegistry))
    .setRetryStrategy(retryStrategy)
    .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create()
        .setDefaultConnectionConfig(connectionConfig)
        .build());

// Usage example
try (CloseableHttpAsyncClient client = asyncClient()) {
    SimpleHttpRequest request = SimpleRequestBuilder.get(server.baseUrl()).build();
    executeAsync(client, request);
}
assertThat(observationRegistry).hasObservationWithNameEqualTo(DEFAULT_METER_NAME)
    .that()
    .hasLowCardinalityKeyValue(OUTCOME.withValue("SUCCESS"))
    .hasLowCardinalityKeyValue(STATUS.withValue("200"))
    .hasLowCardinalityKeyValue(METHOD.withValue("GET"));

Retry Strategy Considerations

HttpClient supports build-in request retry handling. Like micrometer observations, this functionality is implemented as an ExecChainHandler named ChainElement.RETRY. When instrumenting the client, you’ll have to decide whether to observe individual retries or not. If the ObservationExecChainHandler is placed before the retry handler, micrometer will see the initial request and the final outcome after the last retry. Elapsed time will account for any delays imposed by the retry handler. On the other hand, if micrometer is registered after the retry handler, it will be invoked for each individual retry. Elapsed time will measure the individual http requests, but without the backoff delay imposed by the retry handler. The described behaviour and related configuration are the same for both the classic and the async client - with one exception described in the following note.

When using the async client, placing micrometer before the retry handler requires at least version 5.3.x of the HttpComponents Client library. The classic client has no further restrictions.

Example instrumentation code.

// Example: setup instrumentation to meter retries individually
HttpClientBuilder clientBuilder = HttpClients.custom()
    .setRetryStrategy(retryStrategy)
    .addExecInterceptorAfter(ChainElement.RETRY.name(), "micrometer",
            new ObservationExecChainHandler(observationRegistry))
    .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
        .setDefaultConnectionConfig(connectionConfig)
        .build());

// Example: setup instrumentation to aggregate retries
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom()
    .addExecInterceptorFirst("micrometer", new ObservationExecChainHandler(observationRegistry))
    .setRetryStrategy(retryStrategy)
    .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create()
        .setDefaultConnectionConfig(connectionConfig)
        .build());