Skip to content

Commit

Permalink
Provide an option to customize meter name for metrics generated for r…
Browse files Browse the repository at this point in the history
…equests that goes through HttpClient and HttpAsyncClient

Resolved micrometer-metricsgh-3706
  • Loading branch information
Chintan Radia committed Apr 1, 2023
1 parent a350afa commit fe4b380
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,12 @@ public class MicrometerHttpClientInterceptor {
* @param uriMapper URI mapper to create {@code uri} tag
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
* @param meterName meter name
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<HttpRequest, String> uriMapper,
Iterable<Tag> extraTags, boolean exportTagsForRoute) {
Iterable<Tag> extraTags, boolean exportTagsForRoute, String meterName) {
this.requestInterceptor = (request, context) -> timerByHttpContext.put(context,
Timer.resource(meterRegistry, METER_NAME)
Timer.resource(meterRegistry, meterName)
.tags("method", request.getRequestLine().getMethod(), "uri", uriMapper.apply(request)));

this.responseInterceptor = (response, context) -> {
Expand All @@ -84,6 +85,31 @@ public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<Htt
};
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance.
* @param meterRegistry meter registry to bind
* @param uriMapper URI mapper to create {@code uri} tag
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<HttpRequest, String> uriMapper,
Iterable<Tag> extraTags, boolean exportTagsForRoute) {
this(meterRegistry, uriMapper, extraTags, exportTagsForRoute, METER_NAME);
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance with
* {@link DefaultUriMapper}.
* @param meterRegistry meter registry to bind
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
* @param meterName meter name
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Iterable<Tag> extraTags,
boolean exportTagsForRoute, String meterName) {
this(meterRegistry, new DefaultUriMapper(), extraTags, exportTagsForRoute, meterName);
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance with
* {@link DefaultUriMapper}.
Expand All @@ -93,7 +119,7 @@ public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<Htt
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Iterable<Tag> extraTags,
boolean exportTagsForRoute) {
this(meterRegistry, new DefaultUriMapper(), extraTags, exportTagsForRoute);
this(meterRegistry, extraTags, exportTagsForRoute, METER_NAME);
}

public HttpRequestInterceptor getRequestInterceptor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public class MicrometerHttpRequestExecutor extends HttpRequestExecutor {

private final MeterRegistry registry;

private final String meterName;

private final ObservationRegistry observationRegistry;

@Nullable
Expand All @@ -85,12 +87,13 @@ public class MicrometerHttpRequestExecutor extends HttpRequestExecutor {
/**
* Use {@link #builder(MeterRegistry)} to create an instance of this class.
*/
private MicrometerHttpRequestExecutor(int waitForContinue, MeterRegistry registry,
private MicrometerHttpRequestExecutor(int waitForContinue, MeterRegistry registry, String meterName,
Function<HttpRequest, String> uriMapper, Iterable<Tag> extraTags, boolean exportTagsForRoute,
ObservationRegistry observationRegistry, @Nullable ApacheHttpClientObservationConvention convention) {
super(waitForContinue);
this.registry = Optional.ofNullable(registry)
.orElseThrow(() -> new IllegalArgumentException("registry is required but has been initialized with null"));
this.meterName = meterName;
this.uriMapper = Optional.ofNullable(uriMapper)
.orElseThrow(
() -> new IllegalArgumentException("uriMapper is required but has been initialized with null"));
Expand Down Expand Up @@ -132,7 +135,7 @@ public HttpResponse execute(HttpRequest request, HttpClientConnection conn, Http
}
finally {
String status = statusCodeOrError;
sample.stop(METER_NAME, "Duration of Apache HttpClient request execution",
sample.stop(this.meterName, "Duration of Apache HttpClient request execution",
() -> Tags
.of("method", DefaultApacheHttpClientObservationConvention.INSTANCE.getMethodString(request),
"uri", uriMapper.apply(request), "status", status)
Expand All @@ -145,6 +148,8 @@ public static class Builder {

private final MeterRegistry registry;

private String requestsMeterName = MicrometerHttpRequestExecutor.METER_NAME;

private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;

private int waitForContinue = HttpRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE;
Expand Down Expand Up @@ -246,13 +251,25 @@ public Builder observationConvention(ApacheHttpClientObservationConvention conve
return this;
}

/**
* Provide a name to override the default meter name
* @param name Meter name to use when instrumentation is done with Timer. This
* does not have any effect when an
* {@link #observationRegistry(ObservationRegistry)} is configured
* @return This builder instance
*/
public Builder meterName(String name) {
this.requestsMeterName = name;
return this;
}

/**
* Creates an instance of {@link MicrometerHttpRequestExecutor} with all the
* configured properties.
* @return an instance of {@link MicrometerHttpRequestExecutor}
*/
public MicrometerHttpRequestExecutor build() {
return new MicrometerHttpRequestExecutor(waitForContinue, registry, uriMapper, extraTags,
return new MicrometerHttpRequestExecutor(waitForContinue, registry, requestsMeterName, uriMapper, extraTags,
exportTagsForRoute, observationRegistry, observationConvention);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ public class MicrometerHttpClientInterceptor {
* @param uriMapper URI mapper to create {@code uri} tag
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
* @param meterName meter name
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<HttpRequest, String> uriMapper,
Iterable<Tag> extraTags, boolean exportTagsForRoute) {
Iterable<Tag> extraTags, boolean exportTagsForRoute, String meterName) {
this.requestInterceptor = (request, entityDetails, context) -> timerByHttpContext.put(context,
Timer.resource(meterRegistry, METER_NAME)
Timer.resource(meterRegistry, meterName)
.tags("method", request.getMethod(), "uri", uriMapper.apply(request)));

this.responseInterceptor = (response, entityDetails, context) -> {
Expand All @@ -78,6 +79,31 @@ public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<Htt
};
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance.
* @param meterRegistry meter registry to bind
* @param uriMapper URI mapper to create {@code uri} tag
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<HttpRequest, String> uriMapper,
Iterable<Tag> extraTags, boolean exportTagsForRoute) {
this(meterRegistry, uriMapper, extraTags, exportTagsForRoute, METER_NAME);
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance with
* {@link DefaultUriMapper}.
* @param meterRegistry meter registry to bind
* @param extraTags extra tags
* @param exportTagsForRoute whether to export tags for route
* @param meterName meter name
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Iterable<Tag> extraTags,
boolean exportTagsForRoute, String meterName) {
this(meterRegistry, new DefaultUriMapper(), extraTags, exportTagsForRoute, meterName);
}

/**
* Create a {@code MicrometerHttpClientInterceptor} instance with
* {@link DefaultUriMapper}.
Expand All @@ -87,7 +113,7 @@ public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Function<Htt
*/
public MicrometerHttpClientInterceptor(MeterRegistry meterRegistry, Iterable<Tag> extraTags,
boolean exportTagsForRoute) {
this(meterRegistry, new DefaultUriMapper(), extraTags, exportTagsForRoute);
this(meterRegistry, extraTags, exportTagsForRoute, METER_NAME);
}

public HttpRequestInterceptor getRequestInterceptor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public class MicrometerHttpRequestExecutor extends HttpRequestExecutor {

private final MeterRegistry registry;

private final String meterName;

private final ObservationRegistry observationRegistry;

@Nullable
Expand All @@ -72,12 +74,13 @@ public class MicrometerHttpRequestExecutor extends HttpRequestExecutor {
/**
* Use {@link #builder(MeterRegistry)} to create an instance of this class.
*/
private MicrometerHttpRequestExecutor(Timeout waitForContinue, MeterRegistry registry,
private MicrometerHttpRequestExecutor(Timeout waitForContinue, MeterRegistry registry, String meterName,
Function<HttpRequest, String> uriMapper, Iterable<Tag> extraTags, boolean exportTagsForRoute,
ObservationRegistry observationRegistry, @Nullable ApacheHttpClientObservationConvention convention) {
super(waitForContinue, null, null);
this.registry = Optional.ofNullable(registry)
.orElseThrow(() -> new IllegalArgumentException("registry is required but has been initialized with null"));
this.meterName = meterName;
this.uriMapper = Optional.ofNullable(uriMapper)
.orElseThrow(
() -> new IllegalArgumentException("uriMapper is required but has been initialized with null"));
Expand Down Expand Up @@ -119,7 +122,7 @@ public ClassicHttpResponse execute(ClassicHttpRequest request, HttpClientConnect
}
finally {
String status = statusCodeOrError;
sample.stop(METER_NAME, "Duration of Apache HttpClient request execution",
sample.stop(meterName, "Duration of Apache HttpClient request execution",
() -> Tags
.of("method", DefaultApacheHttpClientObservationConvention.INSTANCE.getMethodString(request),
"uri", uriMapper.apply(request), "status", status)
Expand All @@ -132,6 +135,8 @@ public static class Builder {

private final MeterRegistry registry;

private String requestsMeterName = MicrometerHttpRequestExecutor.METER_NAME;

private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;

private Timeout waitForContinue = HttpRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE;
Expand Down Expand Up @@ -231,12 +236,24 @@ public Builder observationConvention(ApacheHttpClientObservationConvention conve
return this;
}

/**
* Provide a name to override the default meter name
* @param name Meter name to use when instrumentation is done with Timer. This
* does not have any effect when an
* {@link #observationRegistry(ObservationRegistry)} is configured
* @return This builder instance
*/
public Builder meterName(String name) {
this.requestsMeterName = name;
return this;
}

/**
* @return Creates an instance of {@link MicrometerHttpRequestExecutor} with all
* the configured properties.
*/
public MicrometerHttpRequestExecutor build() {
return new MicrometerHttpRequestExecutor(waitForContinue, registry, uriMapper, extraTags,
return new MicrometerHttpRequestExecutor(waitForContinue, registry, requestsMeterName, uriMapper, extraTags,
exportTagsForRoute, observationRegistry, observationConvention);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ void uriIsReadFromHttpHeader(@WiremockResolver.Wiremock WireMockServer server) t
client.close();
}

@Test
void overridesDefaultMeterName(@WiremockResolver.Wiremock WireMockServer server) throws Exception {
String meterName = "http.client.requests";
server.stubFor(any(anyUrl()));
MicrometerHttpClientInterceptor interceptor = new MicrometerHttpClientInterceptor(registry, Tags.empty(), true,
meterName);
CloseableHttpAsyncClient client = asyncClient(interceptor);
client.start();
HttpGet request = new HttpGet(server.baseUrl());

Future<HttpResponse> future = client.execute(request, null);
HttpResponse response = future.get();

assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);
assertThat(registry.get(meterName).timer().count()).isEqualTo(1);

client.close();
}

private CloseableHttpAsyncClient asyncClient() {
MicrometerHttpClientInterceptor interceptor = new MicrometerHttpClientInterceptor(registry,
request -> request.getRequestLine().getUri(), Tags.empty(), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ void waitForContinueGetsPassedToSuper() {
assertThat(requestExecutor).hasFieldOrPropertyWithValue("waitForContinue", 1000);
}

@Test
void overridesDefaultMeterName(@WiremockResolver.Wiremock WireMockServer server) throws IOException {
String meterName = "http.client.requests";
MicrometerHttpRequestExecutor executor = MicrometerHttpRequestExecutor.builder(registry)
.meterName(meterName)
.build();
HttpClient client = client(executor);
EntityUtils.consume(client.execute(new HttpGet(server.baseUrl())).getEntity());
assertThat(registry.get(meterName).timer().count()).isEqualTo(1L);
}

@ParameterizedTest
@ValueSource(booleans = { false, true })
void uriMapperWorksAsExpected(boolean configureObservationRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ void uriIsReadFromHttpHeader(@WiremockResolver.Wiremock WireMockServer server) t
client.close();
}

@Test
void overridesDefaultMeterName(@WiremockResolver.Wiremock WireMockServer server) throws Exception {
String meterName = "http.client.requests";
server.stubFor(any(anyUrl()));
MicrometerHttpClientInterceptor interceptor = new MicrometerHttpClientInterceptor(registry, Tags.empty(), true,
meterName);
CloseableHttpAsyncClient client = asyncClient(interceptor);
client.start();
SimpleHttpRequest request = SimpleRequestBuilder.get(server.baseUrl()).build();
request.addHeader(DefaultUriMapper.URI_PATTERN_HEADER, "/some/pattern");

Future<SimpleHttpResponse> future = client.execute(request, null);
HttpResponse response = future.get();

assertThat(response.getCode()).isEqualTo(200);
assertThat(registry.get(meterName).tag("uri", "/some/pattern").tag("status", "200").timer().count())
.isEqualTo(1);

client.close();
}

private CloseableHttpAsyncClient asyncClient() {
MicrometerHttpClientInterceptor interceptor = new MicrometerHttpClientInterceptor(registry,
HttpRequest::getRequestUri, Tags.empty(), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ void waitForContinueGetsPassedToSuper() {
assertThat(requestExecutor).hasFieldOrPropertyWithValue("waitForContinue", Timeout.ofMilliseconds(1000));
}

@Test
void overridesDefaultMeterName(@WiremockResolver.Wiremock WireMockServer server) throws IOException {
String meterName = "http.client.requests";
MicrometerHttpRequestExecutor executor = MicrometerHttpRequestExecutor.builder(registry)
.meterName(meterName)
.build();
CloseableHttpClient client = client(executor);
execute(client, new HttpGet(server.baseUrl() + "/foo"));
assertThat(registry.get(meterName).timer().count()).isEqualTo(1L);
}

@ParameterizedTest
@ValueSource(booleans = { false, true })
void uriMapperWorksAsExpected(boolean configureObservationRegistry,
Expand Down

0 comments on commit fe4b380

Please sign in to comment.