io.micrometer.java11.instrument.binder.jdk.MicrometerHttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micrometer-java11 Show documentation
Show all versions of micrometer-java11 Show documentation
Micrometer core classes that require Java 11
/*
* Copyright 2024 VMware, Inc.
*
* 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 io.micrometer.java11.instrument.binder.jdk;
import io.micrometer.common.lang.Nullable;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.observation.ObservationOrTimerCompatibleInstrumentation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import java.io.IOException;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Function;
/**
* Delegates to an {@link HttpClient} while instrumenting with Micrometer any HTTP calls
* made. Example setup: {@code
* HttpClient observedClient = MicrometerHttpClient.instrumentationBuilder(HttpClient.newHttpClient(), meterRegistry).build();
* }
*
* Inspired by interceptable-http-client.
*
* @author Marcin Grzejszczak
* @since 1.13.0
*/
public class MicrometerHttpClient extends HttpClient {
/**
* Header name for URI pattern.
*/
public static final String URI_PATTERN_HEADER = "URI_PATTERN";
private final MeterRegistry meterRegistry;
private final HttpClient client;
@Nullable
private final ObservationRegistry observationRegistry;
@Nullable
private final HttpClientObservationConvention customObservationConvention;
private final Function uriMapper;
private MicrometerHttpClient(MeterRegistry meterRegistry, HttpClient client,
@Nullable ObservationRegistry observationRegistry,
@Nullable HttpClientObservationConvention customObservationConvention,
Function uriMapper) {
this.meterRegistry = meterRegistry;
this.client = client;
this.observationRegistry = observationRegistry;
this.customObservationConvention = customObservationConvention;
this.uriMapper = uriMapper;
}
/**
* Builder for instrumentation of {@link HttpClient}.
* @param httpClient HttpClient to wrap
* @param meterRegistry meter registry
* @return builder
*/
public static InstrumentationBuilder instrumentationBuilder(HttpClient httpClient, MeterRegistry meterRegistry) {
return new InstrumentationBuilder(httpClient, meterRegistry);
}
/**
* Builder for {@link MicrometerHttpClient}.
*/
public static class InstrumentationBuilder {
private final HttpClient client;
private final MeterRegistry meterRegistry;
@Nullable
private ObservationRegistry observationRegistry;
@Nullable
private HttpClientObservationConvention customObservationConvention;
private Function uriMapper = request -> request.headers()
.firstValue(URI_PATTERN_HEADER)
.orElse("UNKNOWN");
/**
* Creates a new instance of {@link InstrumentationBuilder}.
* @param client client to wrap
* @param meterRegistry a {@link MeterRegistry}
*/
public InstrumentationBuilder(HttpClient client, MeterRegistry meterRegistry) {
this.client = client;
this.meterRegistry = meterRegistry;
}
/**
* Set {@link ObservationRegistry} if you want to use {@link Observation}.
* @param observationRegistry observation registry
* @return this
*/
public InstrumentationBuilder observationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
return this;
}
/**
* When used with {@link ObservationRegistry}, it will override the default
* {@link HttpClientObservationConvention}.
* @param customObservationConvention custom observation convention
* @return this
*/
public InstrumentationBuilder customObservationConvention(
HttpClientObservationConvention customObservationConvention) {
this.customObservationConvention = customObservationConvention;
return this;
}
/**
* Provides custom URI mapper mechanism.
* @param uriMapper URI mapper
* @return this
*/
public InstrumentationBuilder uriMapper(Function uriMapper) {
this.uriMapper = uriMapper;
return this;
}
/**
* Builds a wrapped {@link HttpClient}.
* @return a wrapped {@link HttpClient}
*/
public HttpClient build() {
return new MicrometerHttpClient(this.meterRegistry, this.client, this.observationRegistry,
this.customObservationConvention, this.uriMapper);
}
}
@Override
public Optional cookieHandler() {
return client.cookieHandler();
}
@Override
public Optional connectTimeout() {
return client.connectTimeout();
}
@Override
public Redirect followRedirects() {
return client.followRedirects();
}
@Override
public Optional proxy() {
return client.proxy();
}
@Override
public SSLContext sslContext() {
return client.sslContext();
}
@Override
public SSLParameters sslParameters() {
return client.sslParameters();
}
@Override
public Optional authenticator() {
return client.authenticator();
}
@Override
public Version version() {
return client.version();
}
@Override
public Optional executor() {
return client.executor();
}
@Override
public HttpResponse send(HttpRequest httpRequest, HttpResponse.BodyHandler bodyHandler)
throws IOException, InterruptedException {
HttpRequest.Builder httpRequestBuilder = decorate(httpRequest);
ObservationOrTimerCompatibleInstrumentation instrumentation = observationOrTimer(
httpRequestBuilder);
HttpRequest request = httpRequestBuilder.build();
HttpResponse response = null;
try {
response = client.send(request, bodyHandler);
instrumentation.setResponse(response);
return response;
}
catch (IOException ex) {
instrumentation.setThrowable(ex);
throw ex;
}
finally {
stopObservationOrTimer(instrumentation, request, response);
}
}
private void stopObservationOrTimer(
ObservationOrTimerCompatibleInstrumentation instrumentation, HttpRequest request,
@Nullable HttpResponse res) {
instrumentation.stop(DefaultHttpClientObservationConvention.INSTANCE.getName(), "Timer for JDK's HttpClient",
() -> Tags.of(HttpClientObservationDocumentation.LowCardinalityKeys.METHOD.asString(), request.method(),
HttpClientObservationDocumentation.LowCardinalityKeys.URI.asString(),
DefaultHttpClientObservationConvention.INSTANCE.getUri(request, res, uriMapper),
HttpClientObservationDocumentation.LowCardinalityKeys.STATUS.asString(),
DefaultHttpClientObservationConvention.INSTANCE.getStatus(res),
HttpClientObservationDocumentation.LowCardinalityKeys.OUTCOME.asString(),
DefaultHttpClientObservationConvention.INSTANCE.getOutcome(res)));
}
private ObservationOrTimerCompatibleInstrumentation observationOrTimer(
HttpRequest.Builder httpRequestBuilder) {
return ObservationOrTimerCompatibleInstrumentation.start(this.meterRegistry, this.observationRegistry, () -> {
HttpClientContext context = new HttpClientContext(this.uriMapper);
context.setCarrier(httpRequestBuilder);
return context;
}, this.customObservationConvention, DefaultHttpClientObservationConvention.INSTANCE);
}
@Override
public CompletableFuture> sendAsync(HttpRequest httpRequest,
HttpResponse.BodyHandler bodyHandler) {
return sendAsync(httpRequest, bodyHandler, null);
}
@Override
public CompletableFuture> sendAsync(HttpRequest httpRequest,
HttpResponse.BodyHandler bodyHandler, @Nullable HttpResponse.PushPromiseHandler pushPromiseHandler) {
HttpRequest.Builder httpRequestBuilder = decorate(httpRequest);
ObservationOrTimerCompatibleInstrumentation instrumentation = observationOrTimer(
httpRequestBuilder);
HttpRequest request = httpRequestBuilder.build();
return client.sendAsync(request, bodyHandler, pushPromiseHandler).handle((response, throwable) -> {
instrumentation.setResponse(response);
if (throwable != null) {
instrumentation.setThrowable(throwable);
stopObservationOrTimer(instrumentation, request, response);
throw new CompletionException(throwable);
}
else {
stopObservationOrTimer(instrumentation, request, response);
return response;
}
});
}
private HttpRequest.Builder decorate(HttpRequest httpRequest) {
HttpRequest.Builder builder = HttpRequest.newBuilder(httpRequest.uri());
builder.expectContinue(httpRequest.expectContinue());
httpRequest.headers().map().forEach((key, values) -> values.forEach(value -> builder.header(key, value)));
httpRequest.bodyPublisher()
.ifPresentOrElse(publisher -> builder.method(httpRequest.method(), publisher), () -> {
switch (httpRequest.method()) {
case "GET":
builder.GET();
break;
case "DELETE":
builder.DELETE();
break;
default:
throw new IllegalStateException(httpRequest.method());
}
});
httpRequest.timeout().ifPresent(builder::timeout);
httpRequest.version().ifPresent(builder::version);
return builder;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy