Skip to content

Commit 15a98f9

Browse files
OkHttp3 instrumentation with observation api (#3176)
* WIP * WIP on KeyValuesConvention * Generates OTel semconventions instead of depend on the JARs * Added an ObservationFilter * Adds mechanism to change observation names depending (#3235) * WIP * Introduces naming conventions * Polish * Removing OTel from the repo since OTel is unstable we've decided it to move it out to an experimental repository that will implement all the interfaces that micrometer provides * Added missing headers * Removing the MeterIdSemanticNameProvider and the MeterFilter method for it * Fixed the invalid line entry * Polishing - removed the service locator like solution in ObservationRegistry for naming conventions. Users will have to provide a convention manually (or in frameworks like Boot it will happen for them) - added noop impls for conventions since we have no-null apis * Fixed formatting
1 parent 9f3513a commit 15a98f9

28 files changed

+1652
-181
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ subprojects {
296296
}
297297
}
298298

299-
if (!(project.name in ['micrometer-commons', 'micrometer-observation', 'micrometer-observation-test'])) {
299+
if (!(project.name in ['micrometer-commons', 'micrometer-observation', 'micrometer-observation-conventions', 'micrometer-observation-test'])) {
300300
apply plugin: 'me.champeau.gradle.japicmp'
301301
apply plugin: 'de.undercouch.download'
302302

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

Lines changed: 6 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,10 @@ 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+
3541
@Override
3642
default int compareTo(KeyValue o) {
3743
return getKey().compareTo(o.getKey());
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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(
51+
"Argument [" + this.value + "] does not follow required format for key [" + this.key + "]");
52+
}
53+
return value;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return "tag(" + key + "=" + value + ")";
59+
}
60+
61+
}

micrometer-commons/src/main/java/io/micrometer/common/docs/KeyName.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.micrometer.common.KeyValue;
1919

2020
import java.util.Arrays;
21+
import java.util.function.Predicate;
2122

2223
/**
2324
* Represents a key name used for documenting instrumentation.
@@ -51,4 +52,14 @@ default KeyValue of(String value) {
5152
return KeyValue.of(getKeyName(), value);
5253
}
5354

55+
/**
56+
* Creates a key value for the given key name.
57+
* @param value value for key
58+
* @param validator value validator
59+
* @return key value
60+
*/
61+
default KeyValue of(String value, Predicate<Object> validator) {
62+
return KeyValue.of(getKeyName(), value, validator);
63+
}
64+
5465
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2022 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.common.docs;
17+
18+
/**
19+
* Renames the metric / trace / observation depending on standards.
20+
*
21+
* @author Marcin Grzejszczak
22+
* @since 1.10.0
23+
*/
24+
public interface SemanticNameProvider<T> {
25+
26+
/**
27+
* Will return a standardized name.
28+
* @return name
29+
*/
30+
String getName();
31+
32+
/**
33+
* Returns {@code true} when this {@link SemanticNameProvider} should be applied and a
34+
* new name should be set.
35+
* @param object object against which we determine whether this provider is applicable
36+
* or not
37+
* @return {@code true} when new name should be applied
38+
*/
39+
boolean isApplicable(T object);
40+
41+
}

micrometer-core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ description 'Core module of Micrometer containing instrumentation API and implem
22

33
dependencies {
44
api project(":micrometer-commons")
5+
api project(":micrometer-observation")
56

67
// TODO(anuraaga): HdrHistogram is exposed in the micrometer API but probably shouldn't be
78
api 'org.hdrhistogram:HdrHistogram'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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.common.lang.Nullable;
23+
import io.micrometer.core.instrument.Tag;
24+
import io.micrometer.core.instrument.Tags;
25+
import okhttp3.Request;
26+
import okhttp3.Response;
27+
28+
import java.io.IOException;
29+
import java.lang.reflect.Method;
30+
import java.util.List;
31+
import java.util.function.BiFunction;
32+
import java.util.function.Function;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.Stream;
35+
36+
import static java.util.stream.Collectors.toList;
37+
import static java.util.stream.StreamSupport.stream;
38+
39+
@NonNullApi
40+
@NonNullFields
41+
public class DefaultOkHttpKeyValuesProvider implements OkHttpKeyValuesProvider {
42+
43+
static final boolean REQUEST_TAG_CLASS_EXISTS;
44+
45+
static {
46+
REQUEST_TAG_CLASS_EXISTS = getMethod(Class.class) != null;
47+
}
48+
49+
@Nullable
50+
private static Method getMethod(Class<?>... parameterTypes) {
51+
try {
52+
return Request.class.getMethod("tag", parameterTypes);
53+
}
54+
catch (NoSuchMethodException e) {
55+
return null;
56+
}
57+
}
58+
59+
private static final String TAG_TARGET_SCHEME = "target.scheme";
60+
61+
private static final String TAG_TARGET_HOST = "target.host";
62+
63+
private static final String TAG_TARGET_PORT = "target.port";
64+
65+
private static final String TAG_VALUE_UNKNOWN = "UNKNOWN";
66+
67+
private static final KeyValues TAGS_TARGET_UNKNOWN = KeyValues.of(TAG_TARGET_SCHEME, TAG_VALUE_UNKNOWN,
68+
TAG_TARGET_HOST, TAG_VALUE_UNKNOWN, TAG_TARGET_PORT, TAG_VALUE_UNKNOWN);
69+
70+
@Override
71+
public KeyValues getLowCardinalityKeyValues(OkHttpContext context) {
72+
OkHttpMetricsEventListener.CallState state = context.getState();
73+
Request request = state.request;
74+
boolean requestAvailable = request != null;
75+
Function<Request, String> urlMapper = context.getUrlMapper();
76+
Iterable<Tag> extraTags = context.getExtraTags();
77+
Iterable<BiFunction<Request, Response, Tag>> contextSpecificTags = context.getContextSpecificTags();
78+
Iterable<Tag> unknownRequestTags = context.getUnknownRequestTags();
79+
boolean includeHostTag = context.isIncludeHostTag();
80+
KeyValues keyValues = KeyValues.of("method", requestAvailable ? request.method() : TAG_VALUE_UNKNOWN, "uri",
81+
getUriTag(urlMapper, state, request), "status", getStatusMessage(state.response, state.exception))
82+
.and(tagsToKeyValues(stream(extraTags.spliterator(), false)))
83+
.and(stream(contextSpecificTags.spliterator(), false)
84+
.map(contextTag -> contextTag.apply(request, state.response))
85+
.map(tag -> KeyValue.of(tag.getKey(), tag.getValue())).collect(toList()))
86+
.and(getRequestTags(request, tagsToKeyValues(stream(unknownRequestTags.spliterator(), false))))
87+
.and(generateTagsForRoute(request));
88+
if (includeHostTag) {
89+
keyValues = KeyValues.of(keyValues).and("host",
90+
requestAvailable ? request.url().host() : TAG_VALUE_UNKNOWN);
91+
}
92+
return keyValues;
93+
}
94+
95+
private String getUriTag(Function<Request, String> urlMapper, OkHttpMetricsEventListener.CallState state,
96+
@Nullable Request request) {
97+
if (request == null) {
98+
return TAG_VALUE_UNKNOWN;
99+
}
100+
return state.response != null && (state.response.code() == 404 || state.response.code() == 301) ? "NOT_FOUND"
101+
: urlMapper.apply(request);
102+
}
103+
104+
private String getStatusMessage(@Nullable Response response, @Nullable IOException exception) {
105+
if (exception != null) {
106+
return "IO_ERROR";
107+
}
108+
109+
if (response == null) {
110+
return "CLIENT_ERROR";
111+
}
112+
113+
return Integer.toString(response.code());
114+
}
115+
116+
private Iterable<KeyValue> getRequestTags(@Nullable Request request, Iterable<KeyValue> unknownRequestTags) {
117+
if (request == null) {
118+
return unknownRequestTags;
119+
}
120+
if (REQUEST_TAG_CLASS_EXISTS) {
121+
Tags requestTag = request.tag(Tags.class);
122+
if (requestTag != null) {
123+
return tagsToKeyValues(requestTag.stream());
124+
}
125+
}
126+
Object requestTag = request.tag();
127+
if (requestTag instanceof Tags) {
128+
return tagsToKeyValues(((Tags) requestTag).stream());
129+
}
130+
return KeyValues.empty();
131+
}
132+
133+
private List<KeyValue> tagsToKeyValues(Stream<Tag> requestTag) {
134+
return requestTag.map(tag -> KeyValue.of(tag.getKey(), tag.getValue())).collect(Collectors.toList());
135+
}
136+
137+
private KeyValues generateTagsForRoute(@Nullable Request request) {
138+
if (request == null) {
139+
return TAGS_TARGET_UNKNOWN;
140+
}
141+
return KeyValues.of(TAG_TARGET_SCHEME, request.url().scheme(), TAG_TARGET_HOST, request.url().host(),
142+
TAG_TARGET_PORT, Integer.toString(request.url().port()));
143+
}
144+
145+
}

0 commit comments

Comments
 (0)