diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeadersUtil.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeadersUtil.java index 682cb3da09b0..5e844aedd891 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeadersUtil.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeadersUtil.java @@ -17,31 +17,53 @@ final class CapturedHttpHeadersUtil { // these are naturally bounded because they only store keys listed in - // otel.instrumentation.http.capture-headers.server.request and - // otel.instrumentation.http.capture-headers.server.response - private static final ConcurrentMap>> requestKeysCache = - new ConcurrentHashMap<>(); - private static final ConcurrentMap>> responseKeysCache = - new ConcurrentHashMap<>(); + // otel.instrumentation.http.server.capture-request-headers and + // otel.instrumentation.http.server.capture-response-headers + private static final ConcurrentMap>> + oldSemconvRequestKeysCache = new ConcurrentHashMap<>(); + private static final ConcurrentMap>> + stableSemconvRequestKeysCache = new ConcurrentHashMap<>(); + private static final ConcurrentMap>> + oldSemconvResponseKeysCache = new ConcurrentHashMap<>(); + private static final ConcurrentMap>> + stableSemconvResponseKeysCache = new ConcurrentHashMap<>(); static List lowercase(List names) { return unmodifiableList( names.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList())); } - static AttributeKey> requestAttributeKey(String headerName) { - return requestKeysCache.computeIfAbsent(headerName, n -> createKey("request", n)); + static AttributeKey> oldSemconvRequestAttributeKey(String headerName) { + return oldSemconvRequestKeysCache.computeIfAbsent( + headerName, n -> createOldSemconvKey("request", n)); } - static AttributeKey> responseAttributeKey(String headerName) { - return responseKeysCache.computeIfAbsent(headerName, n -> createKey("response", n)); + static AttributeKey> stableSemconvRequestAttributeKey(String headerName) { + return stableSemconvRequestKeysCache.computeIfAbsent( + headerName, n -> createStableSemconvKey("request", n)); } - private static AttributeKey> createKey(String type, String headerName) { - // headerName is always lowercase, see CapturedHttpHeaders + static AttributeKey> oldSemconvResponseAttributeKey(String headerName) { + return oldSemconvResponseKeysCache.computeIfAbsent( + headerName, n -> createOldSemconvKey("response", n)); + } + + static AttributeKey> stableSemconvResponseAttributeKey(String headerName) { + return stableSemconvResponseKeysCache.computeIfAbsent( + headerName, n -> createStableSemconvKey("response", n)); + } + + private static AttributeKey> createOldSemconvKey(String type, String headerName) { + // headerName is always lowercase, see CapturedHttpHeadersUtil#lowercase String key = "http." + type + ".header." + headerName.replace('-', '_'); return AttributeKey.stringArrayKey(key); } + private static AttributeKey> createStableSemconvKey(String type, String headerName) { + // headerName is always lowercase, see CapturedHttpHeadersUtil#lowercase + String key = "http." + type + ".header." + headerName; + return AttributeKey.stringArrayKey(key); + } + private CapturedHttpHeadersUtil() {} } diff --git a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java index ab72f719346f..c80bca684551 100644 --- a/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java +++ b/instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java @@ -6,8 +6,10 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.lowercase; -import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.requestAttributeKey; -import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.responseAttributeKey; +import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.oldSemconvRequestAttributeKey; +import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.oldSemconvResponseAttributeKey; +import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.stableSemconvRequestAttributeKey; +import static io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeadersUtil.stableSemconvResponseAttributeKey; import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet; import static io.opentelemetry.instrumentation.api.internal.HttpConstants._OTHER; @@ -70,7 +72,12 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST for (String name : capturedRequestHeaders) { List values = getter.getHttpRequestHeader(request, name); if (!values.isEmpty()) { - internalSet(attributes, requestAttributeKey(name), values); + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, oldSemconvRequestAttributeKey(name), values); + } + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, stableSemconvRequestAttributeKey(name), values); + } } } } @@ -115,7 +122,12 @@ public void onEnd( for (String name : capturedResponseHeaders) { List values = getter.getHttpResponseHeader(request, response, name); if (!values.isEmpty()) { - internalSet(attributes, responseAttributeKey(name), values); + if (SemconvStability.emitOldHttpSemconv()) { + internalSet(attributes, oldSemconvResponseAttributeKey(name), values); + } + if (SemconvStability.emitStableHttpSemconv()) { + internalSet(attributes, stableSemconvResponseAttributeKey(name), values); + } } } } diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java index 125dad78be7c..5137f0268928 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java @@ -143,6 +143,9 @@ void normal() { entry( AttributeKey.stringArrayKey("http.request.header.custom_request_header"), asList("123", "456")), + entry( + AttributeKey.stringArrayKey("http.request.header.custom-request-header"), + asList("123", "456")), entry(SemanticAttributes.NET_PEER_NAME, "github.com"), entry(SemanticAttributes.NET_PEER_PORT, 123L), entry(SemanticAttributes.SERVER_ADDRESS, "github.com"), @@ -162,6 +165,9 @@ void normal() { entry( AttributeKey.stringArrayKey("http.response.header.custom_response_header"), asList("654", "321")), + entry( + AttributeKey.stringArrayKey("http.response.header.custom-response-header"), + asList("654", "321")), entry(SemanticAttributes.NET_PROTOCOL_NAME, "http"), entry(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"), entry(SemanticAttributes.NETWORK_PROTOCOL_NAME, "http"), diff --git a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java index e18aceaf4653..d3646575b785 100644 --- a/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java +++ b/instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java @@ -170,6 +170,9 @@ void normal() { entry(SemanticAttributes.CLIENT_ADDRESS, "1.1.1.1"), entry( AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + asList("123", "456")), + entry( + AttributeKey.stringArrayKey("http.request.header.custom-request-header"), asList("123", "456"))); AttributesBuilder endAttributes = Attributes.builder(); @@ -189,6 +192,9 @@ void normal() { entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L), entry( AttributeKey.stringArrayKey("http.response.header.custom_response_header"), + asList("654", "321")), + entry( + AttributeKey.stringArrayKey("http.response.header.custom-response-header"), asList("654", "321"))); } } diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java index d394d8374593..60c4d7f6187d 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java @@ -173,7 +173,7 @@ void normal() { entry(SemanticAttributes.URL_FULL, "http://github.com"), entry(SemanticAttributes.USER_AGENT_ORIGINAL, "okhttp 3.x"), entry( - AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + AttributeKey.stringArrayKey("http.request.header.custom-request-header"), asList("123", "456")), entry(SemanticAttributes.SERVER_ADDRESS, "github.com"), entry(SemanticAttributes.SERVER_PORT, 123L), @@ -187,7 +187,7 @@ void normal() { entry(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 202L), entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L), entry( - AttributeKey.stringArrayKey("http.response.header.custom_response_header"), + AttributeKey.stringArrayKey("http.response.header.custom-response-header"), asList("654", "321")), entry(SemanticAttributes.NETWORK_PROTOCOL_NAME, "http"), entry(SemanticAttributes.NETWORK_PROTOCOL_VERSION, "1.1"), diff --git a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java index 87e035083886..e3e70429fefa 100644 --- a/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java +++ b/instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java @@ -219,7 +219,7 @@ void normal() { entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}"), entry(SemanticAttributes.CLIENT_ADDRESS, "1.1.1.1"), entry( - AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + AttributeKey.stringArrayKey("http.request.header.custom-request-header"), asList("123", "456"))); AttributesBuilder endAttributes = Attributes.builder(); @@ -235,7 +235,7 @@ void normal() { entry(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 202L), entry(SemanticAttributes.HTTP_RESPONSE_BODY_SIZE, 20L), entry( - AttributeKey.stringArrayKey("http.response.header.custom_response_header"), + AttributeKey.stringArrayKey("http.response.header.custom-response-header"), asList("654", "321"))); } diff --git a/instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy b/instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy index 1120cdf73950..270c1cc7b556 100644 --- a/instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy +++ b/instrumentation/jaxrs/jaxrs-common/testing/src/main/groovy/AbstractJaxRsHttpServerTest.groovy @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import io.opentelemetry.instrumentation.api.internal.SemconvStability import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.base.HttpServerTest @@ -296,8 +297,14 @@ abstract class AbstractJaxRsHttpServerTest extends HttpServerTest implemen "$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long } "$SemanticAttributes.HTTP_ROUTE" path if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) { - "http.request.header.x_test_request" { it == ["test"] } - "http.response.header.x_test_response" { it == ["test"] } + if (SemconvStability.emitOldHttpSemconv()) { + "http.request.header.x_test_request" { it == ["test"] } + "http.response.header.x_test_response" { it == ["test"] } + } + if (SemconvStability.emitStableHttpSemconv()) { + "http.request.header.x-test-request" { it == ["test"] } + "http.response.header.x-test-response" { it == ["test"] } + } } } } diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestParametersExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestParametersExtractor.java index f93c23ff4c7d..0c4b693bc304 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestParametersExtractor.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletRequestParametersExtractor.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; import java.util.List; import java.util.Locale; @@ -27,8 +28,10 @@ public class ServletRequestParametersExtractor .getList( "otel.instrumentation.servlet.experimental.capture-request-parameters", emptyList()); - private static final ConcurrentMap>> parameterKeysCache = - new ConcurrentHashMap<>(); + private static final ConcurrentMap>> + oldSemconvParameterKeysCache = new ConcurrentHashMap<>(); + private static final ConcurrentMap>> + stableSemconvParameterKeysCache = new ConcurrentHashMap<>(); private final ServletAccessor accessor; @@ -45,7 +48,12 @@ public void setAttributes( for (String name : CAPTURE_REQUEST_PARAMETERS) { List values = accessor.getRequestParameterValues(request, name); if (!values.isEmpty()) { - consumer.accept(parameterAttributeKey(name), values); + if (SemconvStability.emitOldHttpSemconv()) { + consumer.accept(oldSemconvParameterAttributeKey(name), values); + } + if (SemconvStability.emitStableHttpSemconv()) { + consumer.accept(stableSemconvParameterAttributeKey(name), values); + } } } } @@ -69,15 +77,29 @@ public void onEnd( setAttributes(request, attributes::put); } - private static AttributeKey> parameterAttributeKey(String headerName) { - return parameterKeysCache.computeIfAbsent(headerName, n -> createKey(n)); + private static AttributeKey> oldSemconvParameterAttributeKey(String headerName) { + return oldSemconvParameterKeysCache.computeIfAbsent( + headerName, ServletRequestParametersExtractor::createOldSemconvKey); } - private static AttributeKey> createKey(String parameterName) { + private static AttributeKey> stableSemconvParameterAttributeKey(String headerName) { + return stableSemconvParameterKeysCache.computeIfAbsent( + headerName, ServletRequestParametersExtractor::createStableSemconvKey); + } + + private static AttributeKey> createOldSemconvKey(String parameterName) { // normalize parameter name similarly as is done with header names when header values are // captured as span attributes parameterName = parameterName.toLowerCase(Locale.ROOT); String key = "servlet.request.parameter." + parameterName.replace('-', '_'); return AttributeKey.stringArrayKey(key); } + + private static AttributeKey> createStableSemconvKey(String parameterName) { + // normalize parameter name similarly as is done with header names when header values are + // captured as span attributes + parameterName = parameterName.toLowerCase(Locale.ROOT); + String key = "servlet.request.parameter." + parameterName; + return AttributeKey.stringArrayKey(key); + } } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java index d5d14e6d8eb1..f05d4e90b7f5 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java @@ -23,6 +23,7 @@ import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.instrumentation.test.utils.PortUtils; import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.sdk.trace.data.SpanData; @@ -538,13 +539,28 @@ void captureHttpHeaders() throws Exception { trace.hasSpansSatisfyingExactly( span -> { assertClientSpan(span, uri, method, responseCode, null).hasNoParent(); - span.hasAttributesSatisfying( - equalTo( - AttributeKey.stringArrayKey("http.request.header.x_test_request"), - singletonList("test")), - equalTo( - AttributeKey.stringArrayKey("http.response.header.x_test_response"), - singletonList("test"))); + List attributeAssertions = new ArrayList<>(); + if (SemconvStability.emitOldHttpSemconv()) { + attributeAssertions.add( + equalTo( + AttributeKey.stringArrayKey("http.request.header.x_test_request"), + singletonList("test"))); + attributeAssertions.add( + equalTo( + AttributeKey.stringArrayKey("http.response.header.x_test_response"), + singletonList("test"))); + } + if (SemconvStability.emitStableHttpSemconv()) { + attributeAssertions.add( + equalTo( + AttributeKey.stringArrayKey("http.request.header.x-test-request"), + singletonList("test"))); + attributeAssertions.add( + equalTo( + AttributeKey.stringArrayKey("http.response.header.x-test-response"), + singletonList("test"))); + } + span.hasAttributesSatisfying(attributeAssertions); }, span -> assertServerSpan(span).hasParent(trace.getSpan(0))); }); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java index 2aeea818b96b..8389b1ea5e27 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpServerTest.java @@ -854,15 +854,30 @@ protected SpanDataAssert assertServerSpan( } if (endpoint == CAPTURE_HEADERS) { - assertThat(attrs) - .containsEntry("http.request.header.x_test_request", new String[] {"test"}); - assertThat(attrs) - .containsEntry("http.response.header.x_test_response", new String[] {"test"}); + if (SemconvStability.emitOldHttpSemconv()) { + assertThat(attrs) + .containsEntry("http.request.header.x_test_request", new String[] {"test"}); + assertThat(attrs) + .containsEntry("http.response.header.x_test_response", new String[] {"test"}); + } + if (SemconvStability.emitStableHttpSemconv()) { + assertThat(attrs) + .containsEntry("http.request.header.x-test-request", new String[] {"test"}); + assertThat(attrs) + .containsEntry("http.response.header.x-test-response", new String[] {"test"}); + } } if (endpoint == CAPTURE_PARAMETERS) { - assertThat(attrs) - .containsEntry( - "servlet.request.parameter.test_parameter", new String[] {"test value õäöü"}); + if (SemconvStability.emitOldHttpSemconv()) { + assertThat(attrs) + .containsEntry( + "servlet.request.parameter.test_parameter", new String[] {"test value õäöü"}); + } + if (SemconvStability.emitStableHttpSemconv()) { + assertThat(attrs) + .containsEntry( + "servlet.request.parameter.test-parameter", new String[] {"test value õäöü"}); + } } });