Skip to content

Commit 8bc5297

Browse files
author
Mateusz Rzeszutek
authored
Don't normalize the '-' character in HTTP header names (#9735)
1 parent 5a2f529 commit 8bc5297

File tree

10 files changed

+148
-42
lines changed

10 files changed

+148
-42
lines changed

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

+34-12
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,53 @@
1717
final class CapturedHttpHeadersUtil {
1818

1919
// these are naturally bounded because they only store keys listed in
20-
// otel.instrumentation.http.capture-headers.server.request and
21-
// otel.instrumentation.http.capture-headers.server.response
22-
private static final ConcurrentMap<String, AttributeKey<List<String>>> requestKeysCache =
23-
new ConcurrentHashMap<>();
24-
private static final ConcurrentMap<String, AttributeKey<List<String>>> responseKeysCache =
25-
new ConcurrentHashMap<>();
20+
// otel.instrumentation.http.server.capture-request-headers and
21+
// otel.instrumentation.http.server.capture-response-headers
22+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
23+
oldSemconvRequestKeysCache = new ConcurrentHashMap<>();
24+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
25+
stableSemconvRequestKeysCache = new ConcurrentHashMap<>();
26+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
27+
oldSemconvResponseKeysCache = new ConcurrentHashMap<>();
28+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
29+
stableSemconvResponseKeysCache = new ConcurrentHashMap<>();
2630

2731
static List<String> lowercase(List<String> names) {
2832
return unmodifiableList(
2933
names.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()));
3034
}
3135

32-
static AttributeKey<List<String>> requestAttributeKey(String headerName) {
33-
return requestKeysCache.computeIfAbsent(headerName, n -> createKey("request", n));
36+
static AttributeKey<List<String>> oldSemconvRequestAttributeKey(String headerName) {
37+
return oldSemconvRequestKeysCache.computeIfAbsent(
38+
headerName, n -> createOldSemconvKey("request", n));
3439
}
3540

36-
static AttributeKey<List<String>> responseAttributeKey(String headerName) {
37-
return responseKeysCache.computeIfAbsent(headerName, n -> createKey("response", n));
41+
static AttributeKey<List<String>> stableSemconvRequestAttributeKey(String headerName) {
42+
return stableSemconvRequestKeysCache.computeIfAbsent(
43+
headerName, n -> createStableSemconvKey("request", n));
3844
}
3945

40-
private static AttributeKey<List<String>> createKey(String type, String headerName) {
41-
// headerName is always lowercase, see CapturedHttpHeaders
46+
static AttributeKey<List<String>> oldSemconvResponseAttributeKey(String headerName) {
47+
return oldSemconvResponseKeysCache.computeIfAbsent(
48+
headerName, n -> createOldSemconvKey("response", n));
49+
}
50+
51+
static AttributeKey<List<String>> stableSemconvResponseAttributeKey(String headerName) {
52+
return stableSemconvResponseKeysCache.computeIfAbsent(
53+
headerName, n -> createStableSemconvKey("response", n));
54+
}
55+
56+
private static AttributeKey<List<String>> createOldSemconvKey(String type, String headerName) {
57+
// headerName is always lowercase, see CapturedHttpHeadersUtil#lowercase
4258
String key = "http." + type + ".header." + headerName.replace('-', '_');
4359
return AttributeKey.stringArrayKey(key);
4460
}
4561

62+
private static AttributeKey<List<String>> createStableSemconvKey(String type, String headerName) {
63+
// headerName is always lowercase, see CapturedHttpHeadersUtil#lowercase
64+
String key = "http." + type + ".header." + headerName;
65+
return AttributeKey.stringArrayKey(key);
66+
}
67+
4668
private CapturedHttpHeadersUtil() {}
4769
}

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

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
package io.opentelemetry.instrumentation.api.instrumenter.http;
77

88
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.lowercase;
9-
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.requestAttributeKey;
10-
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.responseAttributeKey;
9+
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.oldSemconvRequestAttributeKey;
10+
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.oldSemconvResponseAttributeKey;
11+
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.stableSemconvRequestAttributeKey;
12+
import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.stableSemconvResponseAttributeKey;
1113
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
1214
import static io.opentelemetry.instrumentation.api.internal.HttpConstants._OTHER;
1315

@@ -70,7 +72,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
7072
for (String name : capturedRequestHeaders) {
7173
List<String> values = getter.getHttpRequestHeader(request, name);
7274
if (!values.isEmpty()) {
73-
internalSet(attributes, requestAttributeKey(name), values);
75+
if (SemconvStability.emitOldHttpSemconv()) {
76+
internalSet(attributes, oldSemconvRequestAttributeKey(name), values);
77+
}
78+
if (SemconvStability.emitStableHttpSemconv()) {
79+
internalSet(attributes, stableSemconvRequestAttributeKey(name), values);
80+
}
7481
}
7582
}
7683
}
@@ -115,7 +122,12 @@ public void onEnd(
115122
for (String name : capturedResponseHeaders) {
116123
List<String> values = getter.getHttpResponseHeader(request, response, name);
117124
if (!values.isEmpty()) {
118-
internalSet(attributes, responseAttributeKey(name), values);
125+
if (SemconvStability.emitOldHttpSemconv()) {
126+
internalSet(attributes, oldSemconvResponseAttributeKey(name), values);
127+
}
128+
if (SemconvStability.emitStableHttpSemconv()) {
129+
internalSet(attributes, stableSemconvResponseAttributeKey(name), values);
130+
}
119131
}
120132
}
121133
}

instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ void normal() {
143143
entry(
144144
AttributeKey.stringArrayKey("http.request.header.custom_request_header"),
145145
asList("123", "456")),
146+
entry(
147+
AttributeKey.stringArrayKey("http.request.header.custom-request-header"),
148+
asList("123", "456")),
146149
entry(SemanticAttributes.NET_PEER_NAME, "github.com"),
147150
entry(SemanticAttributes.NET_PEER_PORT, 123L),
148151
entry(SemanticAttributes.SERVER_ADDRESS, "github.com"),
@@ -162,6 +165,9 @@ void normal() {
162165
entry(
163166
AttributeKey.stringArrayKey("http.response.header.custom_response_header"),
164167
asList("654", "321")),
168+
entry(
169+
AttributeKey.stringArrayKey("http.response.header.custom-response-header"),
170+
asList("654", "321")),
165171
entry(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
166172
entry(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
167173
entry(SemanticAttributes.NETWORK_PROTOCOL_NAME, "http"),

instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ void normal() {
170170
entry(SemanticAttributes.CLIENT_ADDRESS, "1.1.1.1"),
171171
entry(
172172
AttributeKey.stringArrayKey("http.request.header.custom_request_header"),
173+
asList("123", "456")),
174+
entry(
175+
AttributeKey.stringArrayKey("http.request.header.custom-request-header"),
173176
asList("123", "456")));
174177

175178
AttributesBuilder endAttributes = Attributes.builder();
@@ -189,6 +192,9 @@ void normal() {
189192
entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L),
190193
entry(
191194
AttributeKey.stringArrayKey("http.response.header.custom_response_header"),
195+
asList("654", "321")),
196+
entry(
197+
AttributeKey.stringArrayKey("http.response.header.custom-response-header"),
192198
asList("654", "321")));
193199
}
194200
}

instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ void normal() {
173173
entry(SemanticAttributes.URL_FULL, "http://github.com"),
174174
entry(SemanticAttributes.USER_AGENT_ORIGINAL, "okhttp 3.x"),
175175
entry(
176-
AttributeKey.stringArrayKey("http.request.header.custom_request_header"),
176+
AttributeKey.stringArrayKey("http.request.header.custom-request-header"),
177177
asList("123", "456")),
178178
entry(SemanticAttributes.SERVER_ADDRESS, "github.com"),
179179
entry(SemanticAttributes.SERVER_PORT, 123L),
@@ -187,7 +187,7 @@ void normal() {
187187
entry(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 202L),
188188
entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L),
189189
entry(
190-
AttributeKey.stringArrayKey("http.response.header.custom_response_header"),
190+
AttributeKey.stringArrayKey("http.response.header.custom-response-header"),
191191
asList("654", "321")),
192192
entry(SemanticAttributes.NETWORK_PROTOCOL_NAME, "http"),
193193
entry(SemanticAttributes.NETWORK_PROTOCOL_VERSION, "1.1"),

instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ void normal() {
219219
entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}"),
220220
entry(SemanticAttributes.CLIENT_ADDRESS, "1.1.1.1"),
221221
entry(
222-
AttributeKey.stringArrayKey("http.request.header.custom_request_header"),
222+
AttributeKey.stringArrayKey("http.request.header.custom-request-header"),
223223
asList("123", "456")));
224224

225225
AttributesBuilder endAttributes = Attributes.builder();
@@ -235,7 +235,7 @@ void normal() {
235235
entry(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 202L),
236236
entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L),
237237
entry(
238-
AttributeKey.stringArrayKey("http.response.header.custom_response_header"),
238+
AttributeKey.stringArrayKey("http.response.header.custom-response-header"),
239239
asList("654", "321")));
240240
}
241241

instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import io.opentelemetry.instrumentation.api.internal.SemconvStability
67
import io.opentelemetry.instrumentation.test.AgentTestTrait
78
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
89
import io.opentelemetry.instrumentation.test.base.HttpServerTest
@@ -296,8 +297,14 @@ abstract class AbstractJaxRsHttpServerTest<S> extends HttpServerTest<S> implemen
296297
"$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long }
297298
"$SemanticAttributes.HTTP_ROUTE" path
298299
if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) {
299-
"http.request.header.x_test_request" { it == ["test"] }
300-
"http.response.header.x_test_response" { it == ["test"] }
300+
if (SemconvStability.emitOldHttpSemconv()) {
301+
"http.request.header.x_test_request" { it == ["test"] }
302+
"http.response.header.x_test_response" { it == ["test"] }
303+
}
304+
if (SemconvStability.emitStableHttpSemconv()) {
305+
"http.request.header.x-test-request" { it == ["test"] }
306+
"http.response.header.x-test-response" { it == ["test"] }
307+
}
301308
}
302309
}
303310
}

instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestParametersExtractor.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.opentelemetry.api.common.AttributesBuilder;
1212
import io.opentelemetry.context.Context;
1313
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
14+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
1415
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
1516
import java.util.List;
1617
import java.util.Locale;
@@ -27,8 +28,10 @@ public class ServletRequestParametersExtractor<REQUEST, RESPONSE>
2728
.getList(
2829
"otel.instrumentation.servlet.experimental.capture-request-parameters", emptyList());
2930

30-
private static final ConcurrentMap<String, AttributeKey<List<String>>> parameterKeysCache =
31-
new ConcurrentHashMap<>();
31+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
32+
oldSemconvParameterKeysCache = new ConcurrentHashMap<>();
33+
private static final ConcurrentMap<String, AttributeKey<List<String>>>
34+
stableSemconvParameterKeysCache = new ConcurrentHashMap<>();
3235

3336
private final ServletAccessor<REQUEST, RESPONSE> accessor;
3437

@@ -45,7 +48,12 @@ public void setAttributes(
4548
for (String name : CAPTURE_REQUEST_PARAMETERS) {
4649
List<String> values = accessor.getRequestParameterValues(request, name);
4750
if (!values.isEmpty()) {
48-
consumer.accept(parameterAttributeKey(name), values);
51+
if (SemconvStability.emitOldHttpSemconv()) {
52+
consumer.accept(oldSemconvParameterAttributeKey(name), values);
53+
}
54+
if (SemconvStability.emitStableHttpSemconv()) {
55+
consumer.accept(stableSemconvParameterAttributeKey(name), values);
56+
}
4957
}
5058
}
5159
}
@@ -69,15 +77,29 @@ public void onEnd(
6977
setAttributes(request, attributes::put);
7078
}
7179

72-
private static AttributeKey<List<String>> parameterAttributeKey(String headerName) {
73-
return parameterKeysCache.computeIfAbsent(headerName, n -> createKey(n));
80+
private static AttributeKey<List<String>> oldSemconvParameterAttributeKey(String headerName) {
81+
return oldSemconvParameterKeysCache.computeIfAbsent(
82+
headerName, ServletRequestParametersExtractor::createOldSemconvKey);
7483
}
7584

76-
private static AttributeKey<List<String>> createKey(String parameterName) {
85+
private static AttributeKey<List<String>> stableSemconvParameterAttributeKey(String headerName) {
86+
return stableSemconvParameterKeysCache.computeIfAbsent(
87+
headerName, ServletRequestParametersExtractor::createStableSemconvKey);
88+
}
89+
90+
private static AttributeKey<List<String>> createOldSemconvKey(String parameterName) {
7791
// normalize parameter name similarly as is done with header names when header values are
7892
// captured as span attributes
7993
parameterName = parameterName.toLowerCase(Locale.ROOT);
8094
String key = "servlet.request.parameter." + parameterName.replace('-', '_');
8195
return AttributeKey.stringArrayKey(key);
8296
}
97+
98+
private static AttributeKey<List<String>> createStableSemconvKey(String parameterName) {
99+
// normalize parameter name similarly as is done with header names when header values are
100+
// captured as span attributes
101+
parameterName = parameterName.toLowerCase(Locale.ROOT);
102+
String key = "servlet.request.parameter." + parameterName;
103+
return AttributeKey.stringArrayKey(key);
104+
}
83105
}

testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
2424
import io.opentelemetry.instrumentation.test.utils.PortUtils;
2525
import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner;
26+
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
2627
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
2728
import io.opentelemetry.sdk.testing.assertj.TraceAssert;
2829
import io.opentelemetry.sdk.trace.data.SpanData;
@@ -538,13 +539,28 @@ void captureHttpHeaders() throws Exception {
538539
trace.hasSpansSatisfyingExactly(
539540
span -> {
540541
assertClientSpan(span, uri, method, responseCode, null).hasNoParent();
541-
span.hasAttributesSatisfying(
542-
equalTo(
543-
AttributeKey.stringArrayKey("http.request.header.x_test_request"),
544-
singletonList("test")),
545-
equalTo(
546-
AttributeKey.stringArrayKey("http.response.header.x_test_response"),
547-
singletonList("test")));
542+
List<AttributeAssertion> attributeAssertions = new ArrayList<>();
543+
if (SemconvStability.emitOldHttpSemconv()) {
544+
attributeAssertions.add(
545+
equalTo(
546+
AttributeKey.stringArrayKey("http.request.header.x_test_request"),
547+
singletonList("test")));
548+
attributeAssertions.add(
549+
equalTo(
550+
AttributeKey.stringArrayKey("http.response.header.x_test_response"),
551+
singletonList("test")));
552+
}
553+
if (SemconvStability.emitStableHttpSemconv()) {
554+
attributeAssertions.add(
555+
equalTo(
556+
AttributeKey.stringArrayKey("http.request.header.x-test-request"),
557+
singletonList("test")));
558+
attributeAssertions.add(
559+
equalTo(
560+
AttributeKey.stringArrayKey("http.response.header.x-test-response"),
561+
singletonList("test")));
562+
}
563+
span.hasAttributesSatisfying(attributeAssertions);
548564
},
549565
span -> assertServerSpan(span).hasParent(trace.getSpan(0)));
550566
});

testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java

+22-7
Original file line numberDiff line numberDiff line change
@@ -854,15 +854,30 @@ protected SpanDataAssert assertServerSpan(
854854
}
855855

856856
if (endpoint == CAPTURE_HEADERS) {
857-
assertThat(attrs)
858-
.containsEntry("http.request.header.x_test_request", new String[] {"test"});
859-
assertThat(attrs)
860-
.containsEntry("http.response.header.x_test_response", new String[] {"test"});
857+
if (SemconvStability.emitOldHttpSemconv()) {
858+
assertThat(attrs)
859+
.containsEntry("http.request.header.x_test_request", new String[] {"test"});
860+
assertThat(attrs)
861+
.containsEntry("http.response.header.x_test_response", new String[] {"test"});
862+
}
863+
if (SemconvStability.emitStableHttpSemconv()) {
864+
assertThat(attrs)
865+
.containsEntry("http.request.header.x-test-request", new String[] {"test"});
866+
assertThat(attrs)
867+
.containsEntry("http.response.header.x-test-response", new String[] {"test"});
868+
}
861869
}
862870
if (endpoint == CAPTURE_PARAMETERS) {
863-
assertThat(attrs)
864-
.containsEntry(
865-
"servlet.request.parameter.test_parameter", new String[] {"test value õäöü"});
871+
if (SemconvStability.emitOldHttpSemconv()) {
872+
assertThat(attrs)
873+
.containsEntry(
874+
"servlet.request.parameter.test_parameter", new String[] {"test value õäöü"});
875+
}
876+
if (SemconvStability.emitStableHttpSemconv()) {
877+
assertThat(attrs)
878+
.containsEntry(
879+
"servlet.request.parameter.test-parameter", new String[] {"test value õäöü"});
880+
}
866881
}
867882
});
868883

0 commit comments

Comments
 (0)