Skip to content

Commit 248af9f

Browse files
WIP on KeyValuesConvention
1 parent 0367609 commit 248af9f

File tree

17 files changed

+745
-7
lines changed

17 files changed

+745
-7
lines changed

dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def VERSIONS = [
4343
'javax.xml.bind:jaxb-api:2.3.+',
4444
// TODO: Consider where this should live
4545
'io.micrometer:context-propagation-api:1.0.0-M2',
46+
'io.opentelemetry:opentelemetry-semconv:1.14.0-alpha',
4647
'net.sf.ehcache:ehcache:latest.release',
4748
'org.apache.httpcomponents:httpasyncclient:latest.release',
4849
'org.apache.httpcomponents:httpclient:latest.release',

micrometer-commons/src/main/java/io/micrometer/common/KeyValue.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package io.micrometer.common;
1717

18+
import java.util.function.Predicate;
19+
1820
/**
1921
* Key/value pair representing a dimension of a meter used to classify and drill into
2022
* measurements.
@@ -32,6 +34,14 @@ static KeyValue of(String key, String value) {
3234
return new ImmutableKeyValue(key, value);
3335
}
3436

37+
static KeyValue of(String key, Object value, Predicate<Object> validator) {
38+
return new ValidatedKeyValue<>(key, value, validator);
39+
}
40+
41+
static KeyValue ofUnknownValue(String key) {
42+
return KeyValue.of(key, "UNKNOWN");
43+
}
44+
3545
@Override
3646
default int compareTo(KeyValue o) {
3747
return getKey().compareTo(o.getKey());
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright 2022 VMware, Inc.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.common;
17+
18+
import java.util.function.Predicate;
19+
20+
/**
21+
* {@link KeyValue} with value validation.
22+
*
23+
* @param <T>
24+
* @author Marcin Grzejszczak
25+
* @since 1.10.0
26+
*/
27+
class ValidatedKeyValue<T> implements KeyValue {
28+
29+
private final String key;
30+
31+
private final T value;
32+
33+
ValidatedKeyValue(String key, T value, Predicate<Object> validator) {
34+
this.key = key;
35+
this.value = assertValue(validator, value);
36+
}
37+
38+
@Override
39+
public String getKey() {
40+
return this.key;
41+
}
42+
43+
@Override
44+
public String getValue() {
45+
return String.valueOf(this.value);
46+
}
47+
48+
private T assertValue(Predicate<Object> validator, T value) {
49+
if (!validator.test(value)) {
50+
throw new IllegalArgumentException("Argument [" + this.value + "] does not follow required format for key [" + this.key + "]");
51+
}
52+
return value;
53+
}
54+
}

micrometer-core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ dependencies {
101101
testImplementation 'org.postgresql:postgresql'
102102

103103
testImplementation 'org.testcontainers:mongodb'
104+
105+
testImplementation 'io.opentelemetry:opentelemetry-semconv'
104106
}
105107

106108
task shenandoahTest(type: Test) {

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ private static Method getMethod(Class<?>... parameterTypes) {
6868
private static final KeyValues TAGS_TARGET_UNKNOWN = KeyValues.of(TAG_TARGET_SCHEME, TAG_VALUE_UNKNOWN, TAG_TARGET_HOST,
6969
TAG_VALUE_UNKNOWN, TAG_TARGET_PORT, TAG_VALUE_UNKNOWN);
7070

71+
7172
@Override
7273
public KeyValues getLowCardinalityKeyValues(OkHttpContext context) {
7374
OkHttpMetricsEventListener.CallState state = context.getState();
@@ -78,7 +79,6 @@ public KeyValues getLowCardinalityKeyValues(OkHttpContext context) {
7879
Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags = context.getContextSpecificTags();
7980
Iterable<Tag> unknownRequestTags = context.getUnknownRequestTags();
8081
boolean includeHostTag = context.isIncludeHostTag();
81-
8282
KeyValues keyValues = KeyValues.of("method", requestAvailable ? request.method() : TAG_VALUE_UNKNOWN, "uri", getUriTag(urlMapper, state, request),
8383
"status", getStatusMessage(state.response, state.exception))
8484
.and(tagsToKeyValues(stream(extraTags.spliterator(), false)))
@@ -88,11 +88,9 @@ public KeyValues getLowCardinalityKeyValues(OkHttpContext context) {
8888
.collect(toList()))
8989
.and(getRequestTags(request, tagsToKeyValues(stream(unknownRequestTags.spliterator(), false))))
9090
.and(generateTagsForRoute(request));
91-
9291
if (includeHostTag) {
9392
keyValues = KeyValues.of(keyValues).and("host", requestAvailable ? request.url().host() : TAG_VALUE_UNKNOWN);
9493
}
95-
9694
return keyValues;
9795
}
9896

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.micrometer.observation.ObservationRegistry;
2929
import io.micrometer.observation.transport.http.HttpClientRequest;
3030
import io.micrometer.observation.transport.http.HttpClientResponse;
31+
import io.micrometer.observation.transport.http.tags.HttpClientKeyValuesConvention;
3132
import okhttp3.EventListener;
3233
import okhttp3.*;
3334

@@ -70,7 +71,7 @@ public class OkHttpMetricsEventListener extends EventListener {
7071

7172
private final ObservationRegistry observationRegistry;
7273

73-
private final OkHttpKeyValuesProvider keyValuesProvider;
74+
private final Observation.KeyValuesProvider<OkHttpContext> keyValuesProvider;
7475

7576
private final String requestsMetricName;
7677

@@ -95,7 +96,7 @@ protected OkHttpMetricsEventListener(MeterRegistry registry, String requestsMetr
9596
this(registry, ObservationRegistry.NOOP, new DefaultOkHttpKeyValuesProvider(), requestsMetricName, urlMapper, extraTags, contextSpecificTags, emptyList(), true);
9697
}
9798

98-
OkHttpMetricsEventListener(MeterRegistry registry, ObservationRegistry observationRegistry, OkHttpKeyValuesProvider keyValuesProvider, String requestsMetricName, Function<Request, String> urlMapper,
99+
OkHttpMetricsEventListener(MeterRegistry registry, ObservationRegistry observationRegistry, Observation.KeyValuesProvider<OkHttpContext> keyValuesProvider, String requestsMetricName, Function<Request, String> urlMapper,
99100
Iterable<Tag> extraTags, Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags,
100101
Iterable<String> requestTagKeys, boolean includeHostTag) {
101102
this.registry = registry;
@@ -280,7 +281,7 @@ public static class Builder {
280281

281282
private Iterable<String> requestTagKeys = Collections.emptyList();
282283

283-
private OkHttpKeyValuesProvider keyValuesProvider = new DefaultOkHttpKeyValuesProvider();
284+
private OkHttpKeyValuesProvider keyValuesProvider;
284285

285286
Builder(MeterRegistry registry, String name) {
286287
this.registry = registry;
@@ -373,8 +374,24 @@ public Builder requestTagKeys(Iterable<String> requestTagKeys) {
373374
return this;
374375
}
375376

377+
@SuppressWarnings("unchecked")
376378
public OkHttpMetricsEventListener build() {
377-
return new OkHttpMetricsEventListener(registry, observationRegistry, keyValuesProvider, name, uriMapper, tags, contextSpecificTags, requestTagKeys,
379+
Observation.KeyValuesProvider provider = null;
380+
if (this.keyValuesProvider != null) {
381+
provider = this.keyValuesProvider;
382+
}
383+
else if (observationRegistry.isNoOp() || observationRegistry.observationConfig().getKeyValuesConfiguration() == ObservationRegistry.KeyValuesConfiguration.LEGACY) {
384+
provider = new DefaultOkHttpKeyValuesProvider();
385+
}
386+
else if (observationRegistry.observationConfig().getKeyValuesConfiguration() == ObservationRegistry.KeyValuesConfiguration.STANDARDIZED) {
387+
// TODO: Isn't this too much - maybe we should just require the user to set this manually?
388+
provider = new StandardizedOkHttpKeyValuesProvider(observationRegistry.observationConfig().getKeyValuesConvention(HttpClientKeyValuesConvention.class));
389+
}
390+
else {
391+
provider = new Observation.KeyValuesProvider.CompositeKeyValuesProvider(new DefaultOkHttpKeyValuesProvider(), new StandardizedOkHttpKeyValuesProvider(observationRegistry.observationConfig().getKeyValuesConvention(HttpClientKeyValuesConvention.class)));
392+
}
393+
394+
return new OkHttpMetricsEventListener(registry, observationRegistry, provider, name, uriMapper, tags, contextSpecificTags, requestTagKeys,
378395
includeHostTag);
379396
}
380397

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2017 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.core.instrument.binder.okhttp3;
17+
18+
import io.micrometer.common.KeyValue;
19+
import io.micrometer.common.KeyValues;
20+
import io.micrometer.common.lang.NonNullApi;
21+
import io.micrometer.common.lang.NonNullFields;
22+
import io.micrometer.core.instrument.Tag;
23+
import io.micrometer.observation.transport.http.tags.HttpClientKeyValuesConvention;
24+
import okhttp3.Request;
25+
import okhttp3.Response;
26+
27+
import java.util.function.BiFunction;
28+
29+
import static java.util.stream.Collectors.toList;
30+
import static java.util.stream.StreamSupport.stream;
31+
32+
@NonNullApi
33+
@NonNullFields
34+
public class StandardizedOkHttpKeyValuesProvider implements OkHttpKeyValuesProvider {
35+
36+
private final HttpClientKeyValuesConvention keyValuesConvention;
37+
38+
public StandardizedOkHttpKeyValuesProvider(HttpClientKeyValuesConvention keyValuesConvention) {
39+
this.keyValuesConvention = keyValuesConvention;
40+
}
41+
42+
@Override
43+
public KeyValues getLowCardinalityKeyValues(OkHttpContext context) {
44+
OkHttpMetricsEventListener.CallState state = context.getState();
45+
Request request = state.request;
46+
Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags = context.getContextSpecificTags();
47+
// TODO: What to do when there is no request or response - do we set UNKNOWN ?
48+
return KeyValues.of(keyValuesConvention.all(context.getRequest(), context.getResponse()))
49+
.and(stream(contextSpecificTags.spliterator(), false)
50+
.map(contextTag -> contextTag.apply(request, state.response))
51+
.map(tag -> KeyValue.of(tag.getKey(), tag.getValue()))
52+
.collect(toList()));
53+
}
54+
55+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.micrometer.observation.Observation;
2828
import io.micrometer.observation.ObservationHandler;
2929
import io.micrometer.observation.ObservationRegistry;
30+
import io.micrometer.observation.transport.http.tags.OpenTelemetryHttpClientKeyValuesConvention;
3031
import okhttp3.Cache;
3132
import okhttp3.OkHttpClient;
3233
import okhttp3.Request;
@@ -88,6 +89,8 @@ void timeSuccessful(@WiremockResolver.Wiremock WireMockServer server) throws IOE
8889
void timeSuccessfulWithObservation(@WiremockResolver.Wiremock WireMockServer server) throws IOException {
8990
ObservationRegistry observationRegistry = ObservationRegistry.create();
9091
TestHandler testHandler = new TestHandler();
92+
observationRegistry.observationConfig().keyValuesConfiguration(ObservationRegistry.KeyValuesConfiguration.LEGACY_WITH_STANDARDIZED);
93+
observationRegistry.observationConfig().keyValuesConvention(new OpenTelemetryHttpClientKeyValuesConvention());
9194
observationRegistry.observationConfig().observationHandler(testHandler);
9295
observationRegistry.observationConfig().observationHandler(new TimerObservationHandler(registry));
9396
client = new OkHttpClient.Builder().eventListener(defaultListenerBuilder()

micrometer-observation/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ dependencies {
55

66
optionalApi 'io.micrometer:context-propagation-api'
77

8+
optionalApi 'io.opentelemetry:opentelemetry-semconv'
9+
810
// HttpServlet KeyValueProvider
911
optionalApi 'javax.servlet:javax.servlet-api'
1012

micrometer-observation/src/main/java/io/micrometer/observation/Observation.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,16 @@ interface KeyValuesProviderAware<T extends KeyValuesProvider<?>> {
659659

660660
}
661661

662+
/**
663+
* A marker interface for conventions of {@link KeyValues} naming.
664+
*
665+
* @author Marcin Grzejszczak
666+
* @since 1.10.0
667+
*/
668+
interface KeyValuesConvention {
669+
670+
}
671+
662672
/**
663673
* A provider of key values.
664674
*

0 commit comments

Comments
 (0)