Skip to content

Commit 8b767ac

Browse files
author
Mateusz Rzeszutek
authored
Refactor HTTP attributes extractors to use composition over inheritance (#5267)
* Refactor HTTP attributes extractors to use composition over inheritance * Rename remaining variables: *Extractor to *Getter
1 parent cf6f9b7 commit 8b767ac

File tree

109 files changed

+1293
-1388
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+1293
-1388
lines changed

instrumentation-api/src/jmh/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBenchmark.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.opentelemetry.context.Context;
1010
import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders;
1111
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
12+
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter;
1213
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
1314
import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetServerAttributesGetter;
1415
import io.opentelemetry.instrumentation.api.instrumenter.net.NetServerAttributesExtractor;
@@ -40,8 +41,10 @@ public class InstrumenterBenchmark {
4041
Instrumenter.<Void, Void>builder(
4142
OpenTelemetry.noop(),
4243
"benchmark",
43-
HttpSpanNameExtractor.create(ConstantHttpAttributesExtractor.INSTANCE))
44-
.addAttributesExtractor(ConstantHttpAttributesExtractor.INSTANCE)
44+
HttpSpanNameExtractor.create(ConstantHttpAttributesGetter.INSTANCE))
45+
.addAttributesExtractor(
46+
HttpClientAttributesExtractor.create(
47+
ConstantHttpAttributesGetter.INSTANCE, CapturedHttpHeaders.empty()))
4548
.addAttributesExtractor(
4649
NetServerAttributesExtractor.create(new ConstantNetAttributesGetter()))
4750
.newInstrumenter();
@@ -58,72 +61,61 @@ public Context startEnd() {
5861
return context;
5962
}
6063

61-
static class ConstantHttpAttributesExtractor extends HttpClientAttributesExtractor<Void, Void> {
62-
static final HttpClientAttributesExtractor<Void, Void> INSTANCE =
63-
new ConstantHttpAttributesExtractor();
64-
65-
public ConstantHttpAttributesExtractor() {
66-
super(CapturedHttpHeaders.empty());
67-
}
64+
enum ConstantHttpAttributesGetter implements HttpClientAttributesGetter<Void, Void> {
65+
INSTANCE;
6866

6967
@Override
70-
@Nullable
71-
protected String method(Void unused) {
68+
public String method(Void unused) {
7269
return "GET";
7370
}
7471

7572
@Override
76-
@Nullable
77-
protected String url(Void unused) {
73+
public String url(Void unused) {
7874
return "https://opentelemetry.io/benchmark";
7975
}
8076

8177
@Override
82-
protected List<String> requestHeader(Void unused, String name) {
78+
public List<String> requestHeader(Void unused, String name) {
8379
if (name.equalsIgnoreCase("user-agent")) {
8480
return Collections.singletonList("OpenTelemetryBot");
8581
}
8682
return Collections.emptyList();
8783
}
8884

8985
@Override
90-
@Nullable
91-
protected Long requestContentLength(Void unused, @Nullable Void unused2) {
86+
public Long requestContentLength(Void unused, @Nullable Void unused2) {
9287
return 100L;
9388
}
9489

9590
@Override
9691
@Nullable
97-
protected Long requestContentLengthUncompressed(Void unused, @Nullable Void unused2) {
92+
public Long requestContentLengthUncompressed(Void unused, @Nullable Void unused2) {
9893
return null;
9994
}
10095

10196
@Override
102-
@Nullable
103-
protected String flavor(Void unused, @Nullable Void unused2) {
97+
public String flavor(Void unused, @Nullable Void unused2) {
10498
return SemanticAttributes.HttpFlavorValues.HTTP_2_0;
10599
}
106100

107101
@Override
108-
@Nullable
109-
protected Integer statusCode(Void unused, Void unused2) {
102+
public Integer statusCode(Void unused, Void unused2) {
110103
return 200;
111104
}
112105

113106
@Override
114-
@Nullable
115-
protected Long responseContentLength(Void unused, Void unused2) {
107+
public Long responseContentLength(Void unused, Void unused2) {
116108
return 100L;
117109
}
118110

119111
@Override
120112
@Nullable
121-
protected Long responseContentLengthUncompressed(Void unused, Void unused2) {
113+
public Long responseContentLengthUncompressed(Void unused, Void unused2) {
122114
return null;
123115
}
124116

125117
@Override
126-
protected List<String> responseHeader(Void unused, Void unused2, String name) {
118+
public List<String> responseHeader(Void unused, Void unused2, String name) {
127119
return Collections.emptyList();
128120
}
129121
}

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
package io.opentelemetry.instrumentation.api.instrumenter.http;
77

88
import io.opentelemetry.api.common.AttributesBuilder;
9-
import io.opentelemetry.context.Context;
109
import io.opentelemetry.instrumentation.api.config.Config;
11-
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1210
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
1311
import javax.annotation.Nullable;
1412

@@ -21,53 +19,48 @@
2119
* return {@code null} from the protected attribute methods, but implement as many as possible for
2220
* best compliance with the OpenTelemetry specification.
2321
*/
24-
public abstract class HttpClientAttributesExtractor<REQUEST, RESPONSE>
25-
extends HttpCommonAttributesExtractor<REQUEST, RESPONSE> {
22+
public final class HttpClientAttributesExtractor<REQUEST, RESPONSE>
23+
extends HttpCommonAttributesExtractor<
24+
REQUEST, RESPONSE, HttpClientAttributesGetter<REQUEST, RESPONSE>> {
2625

26+
/** Creates the HTTP client attributes extractor with default configuration. */
27+
public static <REQUEST, RESPONSE> HttpClientAttributesExtractor<REQUEST, RESPONSE> create(
28+
HttpClientAttributesGetter<REQUEST, RESPONSE> getter) {
29+
return create(getter, CapturedHttpHeaders.client(Config.get()));
30+
}
31+
32+
// TODO: there should be a builder for all optional attributes
2733
/**
2834
* Creates the HTTP client attributes extractor.
2935
*
3036
* @param capturedHttpHeaders A configuration object specifying which HTTP request and response
3137
* headers should be captured as span attributes.
3238
*/
33-
protected HttpClientAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) {
34-
super(capturedHttpHeaders);
39+
public static <REQUEST, RESPONSE> HttpClientAttributesExtractor<REQUEST, RESPONSE> create(
40+
HttpClientAttributesGetter<REQUEST, RESPONSE> getter,
41+
CapturedHttpHeaders capturedHttpHeaders) {
42+
return new HttpClientAttributesExtractor<>(getter, capturedHttpHeaders);
3543
}
3644

37-
/** Creates the HTTP client attributes extractor with default configuration. */
38-
protected HttpClientAttributesExtractor() {
39-
this(CapturedHttpHeaders.client(Config.get()));
45+
private HttpClientAttributesExtractor(
46+
HttpClientAttributesGetter<REQUEST, RESPONSE> getter,
47+
CapturedHttpHeaders capturedHttpHeaders) {
48+
super(getter, capturedHttpHeaders);
4049
}
4150

4251
@Override
43-
public final void onStart(AttributesBuilder attributes, REQUEST request) {
52+
public void onStart(AttributesBuilder attributes, REQUEST request) {
4453
super.onStart(attributes, request);
45-
set(attributes, SemanticAttributes.HTTP_URL, url(request));
54+
set(attributes, SemanticAttributes.HTTP_URL, getter.url(request));
4655
}
4756

4857
@Override
49-
public final void onEnd(
58+
public void onEnd(
5059
AttributesBuilder attributes,
5160
REQUEST request,
5261
@Nullable RESPONSE response,
5362
@Nullable Throwable error) {
5463
super.onEnd(attributes, request, response, error);
55-
set(attributes, SemanticAttributes.HTTP_FLAVOR, flavor(request, response));
64+
set(attributes, SemanticAttributes.HTTP_FLAVOR, getter.flavor(request, response));
5665
}
57-
58-
// Attributes that always exist in a request
59-
60-
@Nullable
61-
protected abstract String url(REQUEST request);
62-
63-
// Attributes which are not always available when the request is ready.
64-
65-
/**
66-
* Extracts the {@code http.flavor} span attribute.
67-
*
68-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, whether
69-
* {@code response} is {@code null} or not.
70-
*/
71-
@Nullable
72-
protected abstract String flavor(REQUEST request, @Nullable RESPONSE response);
7366
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.api.instrumenter.http;
7+
8+
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
10+
import javax.annotation.Nullable;
11+
12+
/**
13+
* An interface for getting HTTP client attributes.
14+
*
15+
* <p>Instrumentation authors will create implementations of this interface for their specific
16+
* library/framework. It will be used by the {@link HttpClientAttributesExtractor} to obtain the
17+
* various HTTP client attributes in a type-generic way.
18+
*/
19+
public interface HttpClientAttributesGetter<REQUEST, RESPONSE>
20+
extends HttpCommonAttributesGetter<REQUEST, RESPONSE> {
21+
22+
// Attributes that always exist in a request
23+
24+
@Nullable
25+
String url(REQUEST request);
26+
27+
// Attributes which are not always available when the request is ready.
28+
29+
/**
30+
* Extracts the {@code http.flavor} span attribute.
31+
*
32+
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, whether
33+
* {@code response} is {@code null} or not.
34+
*/
35+
@Nullable
36+
String flavor(REQUEST request, @Nullable RESPONSE response);
37+
}

instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java

Lines changed: 14 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpHeaderAttributes.responseAttributeKey;
1010

1111
import io.opentelemetry.api.common.AttributesBuilder;
12-
import io.opentelemetry.context.Context;
1312
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
14-
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1513
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
1614
import java.util.List;
1715
import javax.annotation.Nullable;
@@ -21,22 +19,25 @@
2119
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#common-attributes">HTTP
2220
* attributes</a> that are common to client and server instrumentations.
2321
*/
24-
public abstract class HttpCommonAttributesExtractor<REQUEST, RESPONSE>
22+
abstract class HttpCommonAttributesExtractor<
23+
REQUEST, RESPONSE, GETTER extends HttpCommonAttributesGetter<REQUEST, RESPONSE>>
2524
implements AttributesExtractor<REQUEST, RESPONSE> {
2625

26+
final GETTER getter;
2727
private final CapturedHttpHeaders capturedHttpHeaders;
2828

29-
HttpCommonAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) {
29+
HttpCommonAttributesExtractor(GETTER getter, CapturedHttpHeaders capturedHttpHeaders) {
30+
this.getter = getter;
3031
this.capturedHttpHeaders = capturedHttpHeaders;
3132
}
3233

3334
@Override
3435
public void onStart(AttributesBuilder attributes, REQUEST request) {
35-
set(attributes, SemanticAttributes.HTTP_METHOD, method(request));
36+
set(attributes, SemanticAttributes.HTTP_METHOD, getter.method(request));
3637
set(attributes, SemanticAttributes.HTTP_USER_AGENT, userAgent(request));
3738

3839
for (String name : capturedHttpHeaders.requestHeaders()) {
39-
List<String> values = requestHeader(request, name);
40+
List<String> values = getter.requestHeader(request, name);
4041
if (!values.isEmpty()) {
4142
set(attributes, requestAttributeKey(name), values);
4243
}
@@ -53,114 +54,40 @@ public void onEnd(
5354
set(
5455
attributes,
5556
SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH,
56-
requestContentLength(request, response));
57+
getter.requestContentLength(request, response));
5758
set(
5859
attributes,
5960
SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
60-
requestContentLengthUncompressed(request, response));
61+
getter.requestContentLengthUncompressed(request, response));
6162

6263
if (response != null) {
63-
Integer statusCode = statusCode(request, response);
64+
Integer statusCode = getter.statusCode(request, response);
6465
if (statusCode != null && statusCode > 0) {
6566
set(attributes, SemanticAttributes.HTTP_STATUS_CODE, (long) statusCode);
6667
}
6768
set(
6869
attributes,
6970
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
70-
responseContentLength(request, response));
71+
getter.responseContentLength(request, response));
7172
set(
7273
attributes,
7374
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
74-
responseContentLengthUncompressed(request, response));
75+
getter.responseContentLengthUncompressed(request, response));
7576

7677
for (String name : capturedHttpHeaders.responseHeaders()) {
77-
List<String> values = responseHeader(request, response, name);
78+
List<String> values = getter.responseHeader(request, response, name);
7879
if (!values.isEmpty()) {
7980
set(attributes, responseAttributeKey(name), values);
8081
}
8182
}
8283
}
8384
}
8485

85-
// Attributes that always exist in a request
86-
87-
@Nullable
88-
protected abstract String method(REQUEST request);
89-
9086
@Nullable
9187
private String userAgent(REQUEST request) {
92-
return firstHeaderValue(requestHeader(request, "user-agent"));
88+
return firstHeaderValue(getter.requestHeader(request, "user-agent"));
9389
}
9490

95-
/**
96-
* Extracts all values of header named {@code name} from the request, or an empty list if there
97-
* were none.
98-
*
99-
* <p>Implementations of this method <b>must not</b> return a null value; an empty list should be
100-
* returned instead.
101-
*/
102-
protected abstract List<String> requestHeader(REQUEST request, String name);
103-
104-
// Attributes which are not always available when the request is ready.
105-
106-
/**
107-
* Extracts the {@code http.request_content_length} span attribute.
108-
*
109-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, whether
110-
* {@code response} is {@code null} or not.
111-
*/
112-
@Nullable
113-
protected abstract Long requestContentLength(REQUEST request, @Nullable RESPONSE response);
114-
115-
/**
116-
* Extracts the {@code http.request_content_length_uncompressed} span attribute.
117-
*
118-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, whether
119-
* {@code response} is {@code null} or not.
120-
*/
121-
@Nullable
122-
protected abstract Long requestContentLengthUncompressed(
123-
REQUEST request, @Nullable RESPONSE response);
124-
125-
/**
126-
* Extracts the {@code http.status_code} span attribute.
127-
*
128-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, only when
129-
* {@code response} is non-{@code null}.
130-
*/
131-
@Nullable
132-
protected abstract Integer statusCode(REQUEST request, RESPONSE response);
133-
134-
/**
135-
* Extracts the {@code http.response_content_length} span attribute.
136-
*
137-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, only when
138-
* {@code response} is non-{@code null}.
139-
*/
140-
@Nullable
141-
protected abstract Long responseContentLength(REQUEST request, RESPONSE response);
142-
143-
/**
144-
* Extracts the {@code http.response_content_length_uncompressed} span attribute.
145-
*
146-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, only when
147-
* {@code response} is non-{@code null}.
148-
*/
149-
@Nullable
150-
protected abstract Long responseContentLengthUncompressed(REQUEST request, RESPONSE response);
151-
152-
/**
153-
* Extracts all values of header named {@code name} from the response, or an empty list if there
154-
* were none.
155-
*
156-
* <p>This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, only when
157-
* {@code response} is non-{@code null}.
158-
*
159-
* <p>Implementations of this method <b>must not</b> return a null value; an empty list should be
160-
* returned instead.
161-
*/
162-
protected abstract List<String> responseHeader(REQUEST request, RESPONSE response, String name);
163-
16491
@Nullable
16592
static String firstHeaderValue(List<String> values) {
16693
return values.isEmpty() ? null : values.get(0);

0 commit comments

Comments
 (0)