Skip to content

Commit 75d3442

Browse files
Align http client tags between httpclient and okhttp (#1702)
Apache http client metrics prefixed tags about the target being called with `target.`, while the OkHttp client used `host`. Not only is `host` likely to conflict with a configured common tag, but it differs from the other http client implementation we have metrics for, which can be confusing for users looking at and possibly comparing these metrics. This adds the same `target.` prefixed tags for the OkHttp metrics, namely `host`, `port`, `scheme`. It keeps the previous `host` tag by default for backwards compatibility. However, the `host` tag can be opted out of by using the new `includeHostTag` method on the Builder. Co-authored-by: Tommy Ludwig <[email protected]>
1 parent 74f4a41 commit 75d3442

File tree

2 files changed

+66
-11
lines changed

2 files changed

+66
-11
lines changed

micrometer-core/src/main/java/io/micrometer/core/instrument/binder/okhttp3/OkHttpMetricsEventListener.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
import io.micrometer.core.lang.NonNullApi;
2323
import io.micrometer.core.lang.NonNullFields;
2424
import io.micrometer.core.lang.Nullable;
25-
import okhttp3.*;
25+
import okhttp3.Call;
26+
import okhttp3.EventListener;
27+
import okhttp3.OkHttpClient;
28+
import okhttp3.Request;
29+
import okhttp3.Response;
2630

2731
import java.io.IOException;
2832
import java.lang.reflect.Method;
@@ -77,18 +81,26 @@ private static Method getMethod(String name, Class<?>... parameterTypes) {
7781
private final Function<Request, String> urlMapper;
7882
private final Iterable<Tag> extraTags;
7983
private final Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags;
84+
private final boolean includeHostTag;
8085

8186
// VisibleForTesting
8287
final ConcurrentMap<Call, CallState> callState = new ConcurrentHashMap<>();
8388

8489
protected OkHttpMetricsEventListener(MeterRegistry registry, String requestsMetricName, Function<Request, String> urlMapper,
8590
Iterable<Tag> extraTags,
8691
Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags) {
92+
this(registry, requestsMetricName, urlMapper, extraTags, contextSpecificTags, true);
93+
}
94+
95+
OkHttpMetricsEventListener(MeterRegistry registry, String requestsMetricName, Function<Request, String> urlMapper,
96+
Iterable<Tag> extraTags,
97+
Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags, boolean includeHostTag) {
8798
this.registry = registry;
8899
this.requestsMetricName = requestsMetricName;
89100
this.urlMapper = urlMapper;
90101
this.extraTags = extraTags;
91102
this.contextSpecificTags = contextSpecificTags;
103+
this.includeHostTag = includeHostTag;
92104
}
93105

94106
public static Builder builder(MeterRegistry registry, String name) {
@@ -130,19 +142,19 @@ private void time(CallState state) {
130142
String uri = state.response != null && (state.response.code() == 404 || state.response.code() == 301)
131143
? "NOT_FOUND" : urlMapper.apply(request);
132144

133-
Tags requestTags = requestAvailable ? getRequestTags(request) : Tags.empty();
145+
Tags requestTags = requestAvailable ? getRequestTags(request).and(generateTagsForRoute(request)) : Tags.empty();
134146

135147
Iterable<Tag> tags = Tags.of(
136148
"method", requestAvailable ? request.method() : "UNKNOWN",
137149
"uri", requestAvailable ? uri : "UNKNOWN",
138-
"status", getStatusMessage(state.response, state.exception),
139-
"host", requestAvailable ? request.url().host() : "UNKNOWN"
150+
"status", getStatusMessage(state.response, state.exception)
140151
)
141152
.and(extraTags)
142153
.and(stream(contextSpecificTags.spliterator(), false)
143154
.map(contextTag -> contextTag.apply(request, state.response))
144155
.collect(toList()))
145156
.and(requestTags);
157+
tags = includeHostTag ? Tags.of(tags).and("host", requestAvailable ? request.url().host() : "UNKNOWN") : tags;
146158

147159
Timer.builder(this.requestsMetricName)
148160
.tags(tags)
@@ -151,6 +163,14 @@ private void time(CallState state) {
151163
.record(registry.config().clock().monotonicTime() - state.startTime, TimeUnit.NANOSECONDS);
152164
}
153165

166+
private Tags generateTagsForRoute(Request request) {
167+
return Tags.of(
168+
"target.scheme", request.url().scheme(),
169+
"target.host", request.url().host(),
170+
"target.port", Integer.toString(request.url().port())
171+
);
172+
}
173+
154174
private Tags getRequestTags(Request request) {
155175
if (REQUEST_TAG_CLASS_EXISTS) {
156176
Tags requestTag = request.tag(Tags.class);
@@ -198,6 +218,7 @@ public static class Builder {
198218
private Function<Request, String> uriMapper = (request) -> Optional.ofNullable(request.header(URI_PATTERN)).orElse("none");
199219
private Tags tags = Tags.empty();
200220
private Collection<BiFunction<Request, Response, Tag>> contextSpecificTags = new ArrayList<>();
221+
private boolean includeHostTag = true;
201222

202223
Builder(MeterRegistry registry, String name) {
203224
this.registry = registry;
@@ -238,8 +259,23 @@ public Builder uriMapper(Function<Request, String> uriMapper) {
238259
return this;
239260
}
240261

262+
/**
263+
* Historically, OkHttp Metrics provided by {@link OkHttpMetricsEventListener} included a
264+
* {@code host} tag for the target host being called. To align with other HTTP client metrics,
265+
* this was changed to {@code target.host}, but to maintain backwards compatibility the {@code host}
266+
* tag can also be included. By default, {@code includeHostTag} is {@literal true} so both tags are included.
267+
*
268+
* @param includeHostTag whether to include the {@code host} tag
269+
* @return this builder
270+
* @since 1.5.0
271+
*/
272+
public Builder includeHostTag(boolean includeHostTag) {
273+
this.includeHostTag = includeHostTag;
274+
return this;
275+
}
276+
241277
public OkHttpMetricsEventListener build() {
242-
return new OkHttpMetricsEventListener(registry, name, uriMapper, tags, contextSpecificTags);
278+
return new OkHttpMetricsEventListener(registry, name, uriMapper, tags, contextSpecificTags, includeHostTag);
243279
}
244280
}
245281
}

micrometer-core/src/test/java/io/micrometer/core/instrument/binder/okhttp3/OkHttpMetricsEventListenerTest.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void timeSuccessful(@WiremockResolver.Wiremock WireMockServer server) throws IOE
7373
client.newCall(request).execute().close();
7474

7575
assertThat(registry.get("okhttp.requests")
76-
.tags("foo", "bar", "status", "200", "uri", URI_EXAMPLE_VALUE)
76+
.tags("foo", "bar", "status", "200", "uri", URI_EXAMPLE_VALUE, "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
7777
.timer().count()).isEqualTo(1L);
7878
}
7979

@@ -87,7 +87,7 @@ void timeNotFound(@WiremockResolver.Wiremock WireMockServer server) throws IOExc
8787
client.newCall(request).execute().close();
8888

8989
assertThat(registry.get("okhttp.requests")
90-
.tags("foo", "bar", "status", "404", "uri", "NOT_FOUND")
90+
.tags("foo", "bar", "status", "404", "uri", "NOT_FOUND", "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
9191
.timer().count()).isEqualTo(1L);
9292
}
9393

@@ -115,7 +115,7 @@ void timeFailureDueToTimeout(@WiremockResolver.Wiremock WireMockServer server) {
115115
}
116116

117117
assertThat(registry.get("okhttp.requests")
118-
.tags("foo", "bar", "uri", URI_EXAMPLE_VALUE, "status", "IO_ERROR")
118+
.tags("foo", "bar", "uri", URI_EXAMPLE_VALUE, "status", "IO_ERROR", "target.host", "localhost")
119119
.timer().count()).isEqualTo(1L);
120120
}
121121

@@ -136,7 +136,7 @@ void uriTagWorksWithUriPatternHeader(@WiremockResolver.Wiremock WireMockServer s
136136
client.newCall(request).execute().close();
137137

138138
assertThat(registry.get("okhttp.requests")
139-
.tags("foo", "bar", "uri", "/", "status", "200")
139+
.tags("foo", "bar", "uri", "/", "status", "200", "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
140140
.timer().count()).isEqualTo(1L);
141141
}
142142

@@ -157,7 +157,7 @@ void uriTagWorksWithUriMapper(@WiremockResolver.Wiremock WireMockServer server)
157157
client.newCall(request).execute().close();
158158

159159
assertThat(registry.get("okhttp.requests")
160-
.tags("foo", "bar", "uri", "/helloworld.txt", "status", "200")
160+
.tags("foo", "bar", "uri", "/helloworld.txt", "status", "200", "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
161161
.timer().count()).isEqualTo(1L);
162162
}
163163

@@ -222,6 +222,25 @@ void requestTagsWithoutClass(@WiremockResolver.Wiremock WireMockServer server) t
222222
testRequestTags(server, request);
223223
}
224224

225+
@Test
226+
void hostTagCanBeDisabled(@WiremockResolver.Wiremock WireMockServer server) throws IOException {
227+
server.stubFor(any(anyUrl()));
228+
OkHttpClient client = new OkHttpClient.Builder()
229+
.eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
230+
.includeHostTag(false)
231+
.build())
232+
.build();
233+
Request request = new Request.Builder()
234+
.url(server.baseUrl())
235+
.build();
236+
237+
client.newCall(request).execute().close();
238+
239+
assertThat(registry.get("okhttp.requests")
240+
.tags("status", "200", "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
241+
.timer().getId().getTags()).doesNotContain(Tag.of("host", "localhost"));
242+
}
243+
225244
private void testRequestTags(@WiremockResolver.Wiremock WireMockServer server, Request request) throws IOException {
226245
server.stubFor(any(anyUrl()));
227246
OkHttpClient client = new OkHttpClient.Builder()
@@ -234,7 +253,7 @@ private void testRequestTags(@WiremockResolver.Wiremock WireMockServer server, R
234253
client.newCall(request).execute().close();
235254

236255
assertThat(registry.get("okhttp.requests")
237-
.tags("foo", "bar", "uri", "/helloworld.txt", "status", "200", "requestTag1", "tagValue1")
256+
.tags("foo", "bar", "uri", "/helloworld.txt", "status", "200", "requestTag1", "tagValue1", "target.host", "localhost", "target.port", "" + server.port(), "target.scheme", "http")
238257
.timer().count()).isEqualTo(1L);
239258
}
240259

0 commit comments

Comments
 (0)