diff --git a/benchmark/src/jmh/java/io/opentelemetry/benchmark/InstrumenterBenchmark.java b/benchmark/src/jmh/java/io/opentelemetry/benchmark/InstrumenterBenchmark.java index dae6b51575f1..44ee49a22654 100644 --- a/benchmark/src/jmh/java/io/opentelemetry/benchmark/InstrumenterBenchmark.java +++ b/benchmark/src/jmh/java/io/opentelemetry/benchmark/InstrumenterBenchmark.java @@ -8,11 +8,14 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import org.checkerframework.checker.nullness.qual.Nullable; import org.openjdk.jmh.annotations.Benchmark; @@ -58,6 +61,10 @@ static class ConstantHttpAttributesExtractor extends HttpClientAttributesExtract static final HttpClientAttributesExtractor INSTANCE = new ConstantHttpAttributesExtractor(); + public ConstantHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected @Nullable String method(Void unused) { return "GET"; @@ -73,6 +80,11 @@ static class ConstantHttpAttributesExtractor extends HttpClientAttributesExtract return "OpenTelemetryBot"; } + @Override + protected List requestHeader(Void unused, String name) { + return Collections.emptyList(); + } + @Override protected @Nullable Long requestContentLength(Void unused, @Nullable Void unused2) { return 100L; @@ -102,6 +114,11 @@ static class ConstantHttpAttributesExtractor extends HttpClientAttributesExtract protected @Nullable Long responseContentLengthUncompressed(Void unused, Void unused2) { return null; } + + @Override + protected List responseHeader(Void unused, Void unused2, String name) { + return Collections.emptyList(); + } } static class ConstantNetAttributesExtractor diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ServerInstrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ServerInstrumenter.java index 24efd21d754a..43c1e34db310 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ServerInstrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ServerInstrumenter.java @@ -34,6 +34,8 @@ public Context start(Context parentContext, REQUEST request) { return super.start(extracted, request); } + // TODO: move that to HttpServerAttributesExtractor, now that we have a method for extracting + // header values there private static InstrumenterBuilder addClientIpExtractor( InstrumenterBuilder builder, TextMapGetter getter) { HttpServerAttributesExtractor httpAttributesExtractor = null; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeaders.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeaders.java new file mode 100644 index 000000000000..fc854dc88418 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/CapturedHttpHeaders.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import static java.util.Collections.emptyList; + +import com.google.auto.value.AutoValue; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +/** + * Represents the configuration that specifies which HTTP request/response headers should be + * captured as span attributes. + */ +@AutoValue +public abstract class CapturedHttpHeaders { + + private static final CapturedHttpHeaders EMPTY = create(emptyList(), emptyList()); + + /** Don't capture any HTTP headers as span attributes. */ + public static CapturedHttpHeaders empty() { + return EMPTY; + } + + /** + * Captures the configured HTTP request and response headers as span attributes as described in HTTP + * semantic conventions. + * + *

The HTTP request header values will be captured under the {@code http.request.header.} + * attribute key. The HTTP response header values will be captured under the {@code + * http.response.header.} attribute key. The {@code } part in the attribute key is the + * normalized header name: lowercase, with dashes replaced by underscores. + * + * @param capturedRequestHeaders A list of HTTP request header names that are to be captured as + * span attributes. + * @param capturedResponseHeaders A list of HTTP response header names that are to be captured as + * span attributes. + */ + public static CapturedHttpHeaders create( + List capturedRequestHeaders, List capturedResponseHeaders) { + return new AutoValue_CapturedHttpHeaders( + lowercase(capturedRequestHeaders), lowercase(capturedResponseHeaders)); + } + + private static List lowercase(List names) { + return names.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toList()); + } + + /** Returns the list of HTTP request header names that are to be captured as span attributes. */ + public abstract List requestHeaders(); + + /** Returns the list of HTTP response header names that are to be captured as span attributes. */ + public abstract List responseHeaders(); + + CapturedHttpHeaders() {} +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java index 2476b33044ae..8464b07cd1b4 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractor.java @@ -23,6 +23,16 @@ public abstract class HttpClientAttributesExtractor extends HttpCommonAttributesExtractor { + /** + * Create the HTTP client attributes extractor. + * + * @param capturedHttpHeaders A configuration object specifying which HTTP request and response + * headers should be captured as span attributes. + */ + protected HttpClientAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) { + super(capturedHttpHeaders); + } + @Override protected final void onStart(AttributesBuilder attributes, REQUEST request) { super.onStart(attributes, request); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java index 6fcf11fb0273..64dbc9f10284 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java @@ -5,11 +5,15 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpHeaderAttributes.requestAttributeKey; +import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpHeaderAttributes.responseAttributeKey; + import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -20,13 +24,23 @@ public abstract class HttpCommonAttributesExtractor extends AttributesExtractor { - // directly extending this class should not be possible outside of this package - HttpCommonAttributesExtractor() {} + private final CapturedHttpHeaders capturedHttpHeaders; + + HttpCommonAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) { + this.capturedHttpHeaders = capturedHttpHeaders; + } @Override protected void onStart(AttributesBuilder attributes, REQUEST request) { set(attributes, SemanticAttributes.HTTP_METHOD, method(request)); set(attributes, SemanticAttributes.HTTP_USER_AGENT, userAgent(request)); + + for (String name : capturedHttpHeaders.requestHeaders()) { + List values = requestHeader(request, name); + if (!values.isEmpty()) { + set(attributes, requestAttributeKey(name), values); + } + } } @Override @@ -44,6 +58,7 @@ protected void onEnd( attributes, SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, requestContentLengthUncompressed(request, response)); + if (response != null) { Integer statusCode = statusCode(request, response); if (statusCode != null) { @@ -57,6 +72,13 @@ protected void onEnd( attributes, SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, responseContentLengthUncompressed(request, response)); + + for (String name : capturedHttpHeaders.responseHeaders()) { + List values = responseHeader(request, response, name); + if (!values.isEmpty()) { + set(attributes, responseAttributeKey(name), values); + } + } } } @@ -65,8 +87,21 @@ protected void onEnd( @Nullable protected abstract String method(REQUEST request); + // TODO: remove implementations? @Nullable - protected abstract String userAgent(REQUEST request); + protected String userAgent(REQUEST request) { + List values = requestHeader(request, "user-agent"); + return values.isEmpty() ? null : values.get(0); + } + + /** + * Extracts all values of header named {@code name} from the request, or an empty list if there + * were none. + * + *

Implementations of this method must not return a null value; an empty list should be + * returned instead. + */ + protected abstract List requestHeader(REQUEST request, String name); // Attributes which are not always available when the request is ready. @@ -115,4 +150,16 @@ protected abstract Long requestContentLengthUncompressed( */ @Nullable protected abstract Long responseContentLengthUncompressed(REQUEST request, RESPONSE response); + + /** + * Extracts all values of header named {@code name} from the response, or an empty list if there + * were none. + * + *

This is called from {@link Instrumenter#end(Context, Object, Object, Throwable)}, only when + * {@code response} is non-{@code null}. + * + *

Implementations of this method must not return a null value; an empty list should be + * returned instead. + */ + protected abstract List responseHeader(REQUEST request, RESPONSE response, String name); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java new file mode 100644 index 000000000000..881397e4c584 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.api.caching.Cache; +import java.util.List; + +final class HttpHeaderAttributes { + + private static final Cache>> requestKeysCache = + Cache.newBuilder().setMaximumSize(32).build(); + private static final Cache>> responseKeysCache = + Cache.newBuilder().setMaximumSize(32).build(); + + static AttributeKey> requestAttributeKey(String headerName) { + return requestKeysCache.computeIfAbsent(headerName, n -> createKey("request", n)); + } + + static AttributeKey> responseAttributeKey(String headerName) { + return responseKeysCache.computeIfAbsent(headerName, n -> createKey("response", n)); + } + + private static AttributeKey> createKey(String type, String headerName) { + // headerName is always lowercase, see CapturedHttpHeaders + String key = "http." + type + ".header." + headerName.replace('-', '_'); + return AttributeKey.stringArrayKey(key); + } + + private HttpHeaderAttributes() {} +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java index 57a659a05e1b..9fde41edef26 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractor.java @@ -9,6 +9,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -23,6 +24,16 @@ public abstract class HttpServerAttributesExtractor extends HttpCommonAttributesExtractor { + /** + * Create the HTTP server attributes extractor. + * + * @param capturedHttpHeaders A configuration object specifying which HTTP request and response + * headers should be captured as span attributes. + */ + protected HttpServerAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) { + super(capturedHttpHeaders); + } + @Override protected final void onStart(AttributesBuilder attributes, REQUEST request) { super.onStart(attributes, request); @@ -53,8 +64,12 @@ protected final void onEnd( @Nullable protected abstract String target(REQUEST request); + // TODO: remove implementations? @Nullable - protected abstract String host(REQUEST request); + protected String host(REQUEST request) { + List values = requestHeader(request, "host"); + return values.isEmpty() ? null : values.get(0); + } @Nullable protected abstract String route(REQUEST request); diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 7a4aae4cf103..5d432fd47423 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -273,15 +273,12 @@ void server_http() { otelTesting.getOpenTelemetry(), "test", unused -> "span") .addAttributesExtractors( mockHttpServerAttributes, - mockNetAttributes, + new ConstantNetPeerIpExtractor<>("2.2.2.2"), new AttributesExtractor1(), new AttributesExtractor2()) .addSpanLinksExtractor(new LinksExtractor()) .newServerInstrumenter(new MapGetter()); - when(mockNetAttributes.peerIp(REQUEST, null)).thenReturn("2.2.2.2"); - when(mockNetAttributes.peerIp(REQUEST, RESPONSE)).thenReturn("2.2.2.2"); - Context context = instrumenter.start(Context.root(), REQUEST); SpanContext spanContext = Span.fromContext(context).getSpanContext(); @@ -312,7 +309,7 @@ void server_http_xForwardedFor() { otelTesting.getOpenTelemetry(), "test", unused -> "span") .addAttributesExtractors( mockHttpServerAttributes, - mockNetAttributes, + new ConstantNetPeerIpExtractor<>("2.2.2.2"), new AttributesExtractor1(), new AttributesExtractor2()) .addSpanLinksExtractor(new LinksExtractor()) @@ -322,9 +319,6 @@ void server_http_xForwardedFor() { request.remove("Forwarded"); request.put("X-Forwarded-For", "1.1.1.1"); - when(mockNetAttributes.peerIp(request, null)).thenReturn("2.2.2.2"); - when(mockNetAttributes.peerIp(request, RESPONSE)).thenReturn("2.2.2.2"); - Context context = instrumenter.start(Context.root(), request); SpanContext spanContext = Span.fromContext(context).getSpanContext(); @@ -928,4 +922,34 @@ private static LinkData expectedSpanLink() { SpanContext.create( LINK_TRACE_ID, LINK_SPAN_ID, TraceFlags.getSampled(), TraceState.getDefault())); } + + private static final class ConstantNetPeerIpExtractor + extends NetAttributesExtractor { + + private final String peerIp; + + private ConstantNetPeerIpExtractor(String peerIp) { + this.peerIp = peerIp; + } + + @Override + public String transport(REQUEST request) { + return null; + } + + @Override + public String peerName(REQUEST request, RESPONSE response) { + return null; + } + + @Override + public Integer peerPort(REQUEST request, RESPONSE response) { + return null; + } + + @Override + public String peerIp(REQUEST request, RESPONSE response) { + return peerIp; + } + } } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/db/DbSpanNameExtractorTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/db/DbSpanNameExtractorTest.java index c289b92585a5..bf5a69274f47 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/db/DbSpanNameExtractorTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/db/DbSpanNameExtractorTest.java @@ -24,9 +24,9 @@ void shouldExtractFullSpanName() { // given DbRequest dbRequest = new DbRequest(); - // cannot stub dbOperation() and dbTable() because they're final - given(sqlAttributesExtractor.rawStatement(dbRequest)).willReturn("SELECT * FROM table"); + given(sqlAttributesExtractor.operation(dbRequest)).willReturn("SELECT"); given(sqlAttributesExtractor.name(dbRequest)).willReturn("database"); + given(sqlAttributesExtractor.table(dbRequest)).willReturn("table"); SpanNameExtractor underTest = DbSpanNameExtractor.create(sqlAttributesExtractor); @@ -42,9 +42,9 @@ void shouldSkipDbNameIfTableAlreadyHasDbNamePrefix() { // given DbRequest dbRequest = new DbRequest(); - // cannot stub dbOperation() and dbTable() because they're final - given(sqlAttributesExtractor.rawStatement(dbRequest)).willReturn("SELECT * FROM another.table"); + given(sqlAttributesExtractor.operation(dbRequest)).willReturn("SELECT"); given(sqlAttributesExtractor.name(dbRequest)).willReturn("database"); + given(sqlAttributesExtractor.table(dbRequest)).willReturn("another.table"); SpanNameExtractor underTest = DbSpanNameExtractor.create(sqlAttributesExtractor); @@ -60,8 +60,8 @@ void shouldExtractOperationAndTable() { // given DbRequest dbRequest = new DbRequest(); - // cannot stub dbOperation() and dbTable() because they're final - given(sqlAttributesExtractor.rawStatement(dbRequest)).willReturn("SELECT * FROM table"); + given(sqlAttributesExtractor.operation(dbRequest)).willReturn("SELECT"); + given(sqlAttributesExtractor.table(dbRequest)).willReturn("table"); SpanNameExtractor underTest = DbSpanNameExtractor.create(sqlAttributesExtractor); diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java index 1a0175f5c74f..60f8d4dbaad2 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorTest.java @@ -6,12 +6,16 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.entry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -20,6 +24,10 @@ class HttpClientAttributesExtractorTest { static class TestHttpClientAttributesExtractor extends HttpClientAttributesExtractor, Map> { + TestHttpClientAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) { + super(capturedHttpHeaders); + } + @Override protected String method(Map request) { return request.get("method"); @@ -31,8 +39,8 @@ protected String url(Map request) { } @Override - protected String userAgent(Map request) { - return request.get("userAgent"); + protected List requestHeader(Map request, String name) { + return asList(request.get("header." + name).split(",")); } @Override @@ -67,6 +75,12 @@ protected Long responseContentLengthUncompressed( Map request, Map response) { return Long.parseLong(response.get("responseContentLengthUncompressed")); } + + @Override + protected List responseHeader( + Map request, Map response, String name) { + return asList(response.get("header." + name).split(",")); + } } @Test @@ -74,24 +88,33 @@ void normal() { Map request = new HashMap<>(); request.put("method", "POST"); request.put("url", "http://github.com"); - request.put("userAgent", "okhttp 3.x"); request.put("requestContentLength", "10"); request.put("requestContentLengthUncompressed", "11"); request.put("flavor", "http/2"); + request.put("header.user-agent", "okhttp 3.x"); + request.put("header.custom-request-header", "123,456"); Map response = new HashMap<>(); response.put("statusCode", "202"); response.put("responseContentLength", "20"); response.put("responseContentLengthUncompressed", "21"); + response.put("header.custom-response-header", "654,321"); + + TestHttpClientAttributesExtractor extractor = + new TestHttpClientAttributesExtractor( + CapturedHttpHeaders.create( + singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"))); - TestHttpClientAttributesExtractor extractor = new TestHttpClientAttributesExtractor(); AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, request); assertThat(attributes.build()) .containsOnly( entry(SemanticAttributes.HTTP_METHOD, "POST"), entry(SemanticAttributes.HTTP_URL, "http://github.com"), - entry(SemanticAttributes.HTTP_USER_AGENT, "okhttp 3.x")); + entry(SemanticAttributes.HTTP_USER_AGENT, "okhttp 3.x"), + entry( + AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + asList("123", "456"))); extractor.onEnd(attributes, request, response, null); assertThat(attributes.build()) @@ -99,11 +122,17 @@ void normal() { entry(SemanticAttributes.HTTP_METHOD, "POST"), entry(SemanticAttributes.HTTP_URL, "http://github.com"), entry(SemanticAttributes.HTTP_USER_AGENT, "okhttp 3.x"), + entry( + AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + asList("123", "456")), entry(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, 10L), entry(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, 11L), entry(SemanticAttributes.HTTP_FLAVOR, "http/2"), entry(SemanticAttributes.HTTP_STATUS_CODE, 202L), entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 20L), - entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 21L)); + entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 21L), + entry( + AttributeKey.stringArrayKey("http.response.header.custom_response_header"), + asList("654", "321"))); } } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java index 2088d9f59c41..7b9ab8ecbc31 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorTest.java @@ -6,12 +6,16 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.entry; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -20,6 +24,10 @@ class HttpServerAttributesExtractorTest { static class TestHttpServerAttributesExtractor extends HttpServerAttributesExtractor, Map> { + TestHttpServerAttributesExtractor(CapturedHttpHeaders capturedHttpHeaders) { + super(capturedHttpHeaders); + } + @Override protected String method(Map request) { return request.get("method"); @@ -30,11 +38,6 @@ protected String target(Map request) { return request.get("target"); } - @Override - protected String host(Map request) { - return request.get("host"); - } - @Override protected String route(Map request) { return request.get("route"); @@ -51,8 +54,8 @@ protected String serverName(Map request, Map res } @Override - protected String userAgent(Map request) { - return request.get("userAgent"); + protected List requestHeader(Map request, String name) { + return asList(request.get("header." + name).split(",")); } @Override @@ -87,6 +90,12 @@ protected Long responseContentLengthUncompressed( Map request, Map response) { return Long.parseLong(response.get("responseContentLengthUncompressed")); } + + @Override + protected List responseHeader( + Map request, Map response, String name) { + return asList(response.get("header." + name).split(",")); + } } @Test @@ -95,49 +104,63 @@ void normal() { request.put("method", "POST"); request.put("url", "http://github.com"); request.put("target", "/repositories/1"); - request.put("host", "github.com:80"); request.put("scheme", "https"); - request.put("userAgent", "okhttp 3.x"); request.put("requestContentLength", "10"); request.put("requestContentLengthUncompressed", "11"); request.put("flavor", "http/2"); request.put("route", "/repositories/{id}"); request.put("serverName", "server"); + request.put("header.user-agent", "okhttp 3.x"); + request.put("header.host", "github.com"); + request.put("header.custom-request-header", "123,456"); Map response = new HashMap<>(); response.put("statusCode", "202"); response.put("responseContentLength", "20"); response.put("responseContentLengthUncompressed", "21"); + response.put("header.custom-response-header", "654,321"); + + TestHttpServerAttributesExtractor extractor = + new TestHttpServerAttributesExtractor( + CapturedHttpHeaders.create( + singletonList("Custom-Request-Header"), singletonList("Custom-Response-Header"))); - TestHttpServerAttributesExtractor extractor = new TestHttpServerAttributesExtractor(); AttributesBuilder attributes = Attributes.builder(); extractor.onStart(attributes, request); assertThat(attributes.build()) .containsOnly( entry(SemanticAttributes.HTTP_FLAVOR, "http/2"), entry(SemanticAttributes.HTTP_METHOD, "POST"), - entry(SemanticAttributes.HTTP_TARGET, "/repositories/1"), - entry(SemanticAttributes.HTTP_HOST, "github.com:80"), entry(SemanticAttributes.HTTP_SCHEME, "https"), + entry(SemanticAttributes.HTTP_HOST, "github.com"), + entry(SemanticAttributes.HTTP_TARGET, "/repositories/1"), entry(SemanticAttributes.HTTP_USER_AGENT, "okhttp 3.x"), - entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}")); + entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}"), + entry( + AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + asList("123", "456"))); extractor.onEnd(attributes, request, response, null); assertThat(attributes.build()) .containsOnly( entry(SemanticAttributes.HTTP_METHOD, "POST"), - entry(SemanticAttributes.HTTP_TARGET, "/repositories/1"), - entry(SemanticAttributes.HTTP_HOST, "github.com:80"), - entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}"), entry(SemanticAttributes.HTTP_SCHEME, "https"), + entry(SemanticAttributes.HTTP_HOST, "github.com"), + entry(SemanticAttributes.HTTP_TARGET, "/repositories/1"), entry(SemanticAttributes.HTTP_USER_AGENT, "okhttp 3.x"), entry(SemanticAttributes.HTTP_ROUTE, "/repositories/{id}"), + entry( + AttributeKey.stringArrayKey("http.request.header.custom_request_header"), + asList("123", "456")), entry(SemanticAttributes.HTTP_SERVER_NAME, "server"), entry(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, 10L), entry(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, 11L), entry(SemanticAttributes.HTTP_FLAVOR, "http/2"), entry(SemanticAttributes.HTTP_STATUS_CODE, 202L), entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 20L), - entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 21L)); + entry(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 21L), + entry( + AttributeKey.stringArrayKey("http.response.header.custom_response_header"), + asList("654", "321"))); } } diff --git a/instrumentation-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/instrumentation-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000000..1f0955d450f0 --- /dev/null +++ b/instrumentation-api/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesExtractor.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesExtractor.java index f3b909c90b92..556d6d571fa9 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesExtractor.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpAsyncClientHttpAttributesExtractor.java @@ -5,7 +5,11 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient; +import static io.opentelemetry.javaagent.instrumentation.apachehttpasyncclient.ApacheHttpClientRequest.headersToList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; +import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.checkerframework.checker.nullness.qual.Nullable; @@ -13,6 +17,10 @@ final class ApacheHttpAsyncClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + ApacheHttpAsyncClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(ApacheHttpClientRequest request) { return request.getMethod(); @@ -24,9 +32,8 @@ protected String url(ApacheHttpClientRequest request) { } @Override - @Nullable - protected String userAgent(ApacheHttpClientRequest request) { - return request.getHeader("User-Agent"); + protected List requestHeader(ApacheHttpClientRequest request, String name) { + return request.getHeader(name); } @Override @@ -68,4 +75,10 @@ protected Long responseContentLengthUncompressed( ApacheHttpClientRequest request, HttpResponse response) { return null; } + + @Override + protected List responseHeader( + ApacheHttpClientRequest request, HttpResponse response, String name) { + return headersToList(response.getHeaders(name)); + } } diff --git a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java index f3be4bab0087..118efd19e4c9 100644 --- a/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java +++ b/instrumentation/apache-httpasyncclient-4.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpasyncclient/ApacheHttpClientRequest.java @@ -8,6 +8,9 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -34,9 +37,20 @@ public ApacheHttpClientRequest(HttpHost httpHost, HttpRequest httpRequest) { delegate = httpRequest; } - public String getHeader(String name) { - Header header = delegate.getFirstHeader(name); - return header != null ? header.getValue() : null; + public List getHeader(String name) { + return headersToList(delegate.getHeaders(name)); + } + + // minimize memory overhead by not using streams + static List headersToList(Header[] headers) { + if (headers.length == 0) { + return Collections.emptyList(); + } + List headersList = new ArrayList<>(headers.length); + for (int i = 0; i < headers.length; ++i) { + headersList.set(i, headers[i].getValue()); + } + return headersList; } public void setHeader(String name, String value) { diff --git a/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientHttpAttributesExtractor.java b/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientHttpAttributesExtractor.java index 14dac946513f..4cda8ad453c6 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientHttpAttributesExtractor.java +++ b/instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v2_0/ApacheHttpClientHttpAttributesExtractor.java @@ -5,8 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v2_0; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpMethod; @@ -17,6 +22,10 @@ final class ApacheHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + ApacheHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(HttpMethod request) { return request.getName(); @@ -34,6 +43,12 @@ protected String userAgent(HttpMethod request) { return header != null ? header.getValue() : null; } + @Override + protected List requestHeader(HttpMethod request, String name) { + Header header = request.getRequestHeader(name); + return header == null ? emptyList() : singletonList(header.getValue()); + } + @Override @Nullable protected Long requestContentLength(HttpMethod request, @Nullable HttpMethod response) { @@ -77,6 +92,12 @@ protected Long responseContentLengthUncompressed(HttpMethod request, HttpMethod return null; } + @Override + protected List responseHeader(HttpMethod request, HttpMethod response, String name) { + Header header = response.getResponseHeader(name); + return header == null ? emptyList() : singletonList(header.getValue()); + } + // mirroring implementation HttpMethodBase.getURI(), to avoid converting to URI and back to String private static String getUrl(HttpMethod request) { HostConfiguration hostConfiguration = request.getHostConfiguration(); diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesExtractor.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesExtractor.java index ba7421937dc8..72f67311bf65 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesExtractor.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientHttpAttributesExtractor.java @@ -5,13 +5,21 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0; +import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v4_0.ApacheHttpClientRequest.headersToList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; +import java.util.List; import org.apache.http.HttpResponse; import org.checkerframework.checker.nullness.qual.Nullable; final class ApacheHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + ApacheHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(ApacheHttpClientRequest request) { return request.getMethod(); @@ -23,9 +31,8 @@ protected String url(ApacheHttpClientRequest request) { } @Override - @Nullable - protected String userAgent(ApacheHttpClientRequest request) { - return request.getHeader("User-Agent"); + protected List requestHeader(ApacheHttpClientRequest request, String name) { + return request.getHeader(name); } @Override @@ -65,4 +72,10 @@ protected Long responseContentLengthUncompressed( ApacheHttpClientRequest request, HttpResponse response) { return null; } + + @Override + protected List responseHeader( + ApacheHttpClientRequest request, HttpResponse response, String name) { + return headersToList(response.getHeaders(name)); + } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java index 354beb8bfcf9..29d77363f7a9 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v4_0/ApacheHttpClientRequest.java @@ -8,6 +8,9 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -40,9 +43,20 @@ public ApacheHttpClientRequest(HttpUriRequest httpRequest) { delegate = httpRequest; } - public String getHeader(String name) { - Header header = delegate.getFirstHeader(name); - return header != null ? header.getValue() : null; + public List getHeader(String name) { + return headersToList(delegate.getHeaders(name)); + } + + // minimize memory overhead by not using streams + static List headersToList(Header[] headers) { + if (headers.length == 0) { + return Collections.emptyList(); + } + List headersList = new ArrayList<>(headers.length); + for (int i = 0; i < headers.length; ++i) { + headersList.set(i, headers[i].getValue()); + } + return headersList; } public void setHeader(String name, String value) { diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientHttpAttributesExtractor.java b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientHttpAttributesExtractor.java index ef9444b750e7..c17a0d4fc483 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientHttpAttributesExtractor.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientHttpAttributesExtractor.java @@ -5,13 +5,22 @@ package io.opentelemetry.instrumentation.apachehttpclient.v4_3; +import static io.opentelemetry.instrumentation.apachehttpclient.v4_3.ApacheHttpClientRequest.headersToList; + +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import java.util.List; import org.apache.http.HttpResponse; import org.checkerframework.checker.nullness.qual.Nullable; final class ApacheHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + // TODO: add support for capturing HTTP headers in library instrumentations + ApacheHttpClientHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(ApacheHttpClientRequest request) { return request.getMethod(); @@ -24,9 +33,8 @@ protected String url(ApacheHttpClientRequest request) { } @Override - @Nullable - protected String userAgent(ApacheHttpClientRequest request) { - return request.getHeader("User-Agent"); + protected List requestHeader(ApacheHttpClientRequest request, String name) { + return request.getHeader(name); } @Override @@ -66,4 +74,10 @@ protected Long responseContentLengthUncompressed( ApacheHttpClientRequest request, HttpResponse response) { return null; } + + @Override + protected List responseHeader( + ApacheHttpClientRequest request, HttpResponse response, String name) { + return headersToList(response.getHeaders(name)); + } } diff --git a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientRequest.java b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientRequest.java index db8263035ab6..2845b2b4d843 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientRequest.java +++ b/instrumentation/apache-httpclient/apache-httpclient-4.3/library/src/main/java/io/opentelemetry/instrumentation/apachehttpclient/v4_3/ApacheHttpClientRequest.java @@ -8,6 +8,9 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -39,10 +42,20 @@ public HttpRequest getDelegate() { return delegate; } - @Nullable - String getHeader(String name) { - Header header = delegate.getFirstHeader(name); - return header != null ? header.getValue() : null; + List getHeader(String name) { + return headersToList(delegate.getHeaders(name)); + } + + // minimize memory overhead by not using streams + static List headersToList(Header[] headers) { + if (headers.length == 0) { + return Collections.emptyList(); + } + List headersList = new ArrayList<>(headers.length); + for (int i = 0; i < headers.length; ++i) { + headersList.set(i, headers[i].getValue()); + } + return headersList; } void setHeader(String name, String value) { diff --git a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesExtractor.java b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesExtractor.java index 18f4e7d93b85..d92866717ae4 100644 --- a/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesExtractor.java +++ b/instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachehttpclient/v5_0/ApacheHttpClientHttpAttributesExtractor.java @@ -6,7 +6,11 @@ package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; @@ -22,6 +26,10 @@ final class ApacheHttpClientHttpAttributesExtractor private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClientHttpAttributesExtractor.class); + ApacheHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(ClassicHttpRequest request) { return request.getMethod(); @@ -67,6 +75,11 @@ protected String userAgent(ClassicHttpRequest request) { return header != null ? header.getValue() : null; } + @Override + protected List requestHeader(ClassicHttpRequest request, String name) { + return headersToList(request.getHeaders(name)); + } + @Override @Nullable protected Long requestContentLength(ClassicHttpRequest request, @Nullable HttpResponse response) { @@ -123,4 +136,22 @@ protected Long responseContentLengthUncompressed( ClassicHttpRequest request, HttpResponse response) { return null; } + + @Override + protected List responseHeader( + ClassicHttpRequest request, HttpResponse response, String name) { + return headersToList(response.getHeaders(name)); + } + + // minimize memory overhead by not using streams + private static List headersToList(Header[] headers) { + if (headers.length == 0) { + return Collections.emptyList(); + } + List headersList = new ArrayList<>(headers.length); + for (int i = 0; i < headers.length; ++i) { + headersList.set(i, headers[i].getValue()); + } + return headersList; + } } diff --git a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientAttributesExtractor.java b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientAttributesExtractor.java index 3ed6750d74cb..71ff43188ffc 100644 --- a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientAttributesExtractor.java +++ b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpClientAttributesExtractor.java @@ -11,13 +11,20 @@ import com.linecorp.armeria.common.RequestContext; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.logging.RequestLog; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class ArmeriaHttpClientAttributesExtractor extends HttpClientAttributesExtractor { + // TODO: add support for capturing HTTP headers in library instrumentations + ArmeriaHttpClientAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(RequestContext ctx) { return ctx.method().name(); @@ -34,6 +41,11 @@ protected String userAgent(RequestContext ctx) { return request(ctx).headers().get(HttpHeaderNames.USER_AGENT); } + @Override + protected List requestHeader(RequestContext ctx, String name) { + return request(ctx).headers().getAll(name); + } + @Override @Nullable protected Long requestContentLength(RequestContext ctx, @Nullable RequestLog requestLog) { @@ -81,6 +93,11 @@ protected Long responseContentLengthUncompressed(RequestContext ctx, RequestLog return null; } + @Override + protected List responseHeader(RequestContext ctx, RequestLog requestLog, String name) { + return requestLog.responseHeaders().getAll(name); + } + private static HttpRequest request(RequestContext ctx) { HttpRequest request = ctx.request(); if (request == null) { diff --git a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpServerAttributesExtractor.java b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpServerAttributesExtractor.java index 58bc3c533d71..19efb73ea962 100644 --- a/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpServerAttributesExtractor.java +++ b/instrumentation/armeria-1.3/library/src/main/java/io/opentelemetry/instrumentation/armeria/v1_3/ArmeriaHttpServerAttributesExtractor.java @@ -12,13 +12,20 @@ import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.common.logging.RequestLog; import com.linecorp.armeria.server.ServiceRequestContext; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class ArmeriaHttpServerAttributesExtractor extends HttpServerAttributesExtractor { + // TODO: add support for capturing HTTP headers in library instrumentations + ArmeriaHttpServerAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(RequestContext ctx) { return ctx.method().name(); @@ -47,6 +54,11 @@ protected String userAgent(RequestContext ctx) { return request(ctx).headers().get(HttpHeaderNames.USER_AGENT); } + @Override + protected List requestHeader(RequestContext ctx, String name) { + return request(ctx).headers().getAll(name); + } + @Override @Nullable protected Long requestContentLength(RequestContext ctx, @Nullable RequestLog requestLog) { @@ -94,6 +106,11 @@ protected Long responseContentLengthUncompressed(RequestContext ctx, RequestLog return null; } + @Override + protected List responseHeader(RequestContext ctx, RequestLog requestLog, String name) { + return requestLog.responseHeaders().getAll(name); + } + @Override @Nullable protected String serverName(RequestContext ctx, @Nullable RequestLog requestLog) { diff --git a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientHttpAttributesExtractor.java b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientHttpAttributesExtractor.java index 1bf5f526d5a8..f3889db42f26 100644 --- a/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientHttpAttributesExtractor.java +++ b/instrumentation/async-http-client/async-http-client-1.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v1_9/AsyncHttpClientHttpAttributesExtractor.java @@ -8,12 +8,19 @@ import com.ning.http.client.Request; import com.ning.http.client.Response; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class AsyncHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + AsyncHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(Request request) { return request.getMethod(); @@ -25,9 +32,8 @@ protected String url(Request request) { } @Override - @Nullable - protected String userAgent(Request request) { - return null; + protected List requestHeader(Request request, String name) { + return request.getHeaders().getOrDefault(name, Collections.emptyList()); } @Override @@ -63,4 +69,9 @@ protected Long responseContentLength(Request request, Response response) { protected Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.getHeaders().getOrDefault(name, Collections.emptyList()); + } } diff --git a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientHttpAttributesExtractor.java b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientHttpAttributesExtractor.java index 51c9ace40990..16b307f5d346 100644 --- a/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientHttpAttributesExtractor.java +++ b/instrumentation/async-http-client/async-http-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/asynchttpclient/v2_0/AsyncHttpClientHttpAttributesExtractor.java @@ -6,7 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.asynchttpclient.v2_0; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.asynchttpclient.Response; import org.asynchttpclient.netty.request.NettyRequest; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,6 +16,10 @@ final class AsyncHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + AsyncHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(RequestContext requestContext) { return requestContext.getRequest().getMethod(); @@ -25,9 +31,8 @@ protected String url(RequestContext requestContext) { } @Override - @Nullable - protected String userAgent(RequestContext requestContext) { - return null; + protected List requestHeader(RequestContext requestContext, String name) { + return requestContext.getRequest().getHeaders().getAll(name); } @Override @@ -84,4 +89,10 @@ protected Long responseContentLengthUncompressed( RequestContext requestContext, Response response) { return null; } + + @Override + protected List responseHeader( + RequestContext requestContext, Response response, String name) { + return response.getHeaders().getAll(name); + } } diff --git a/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientHttpAttributesExtractor.java b/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientHttpAttributesExtractor.java index 2f8786ea2405..cc0a139d2384 100644 --- a/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientHttpAttributesExtractor.java +++ b/instrumentation/google-http-client-1.19/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/GoogleHttpClientHttpAttributesExtractor.java @@ -8,12 +8,18 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class GoogleHttpClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + GoogleHttpClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected @Nullable String method(HttpRequest httpRequest) { return httpRequest.getRequestMethod(); @@ -29,6 +35,11 @@ protected String url(HttpRequest httpRequest) { return httpRequest.getHeaders().getUserAgent(); } + @Override + protected List requestHeader(HttpRequest httpRequest, String name) { + return httpRequest.getHeaders().getHeaderStringValues(name); + } + @Override protected @Nullable Long requestContentLength( HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { @@ -62,4 +73,10 @@ protected String flavor(HttpRequest httpRequest, @Nullable HttpResponse httpResp HttpRequest httpRequest, HttpResponse httpResponse) { return null; } + + @Override + protected List responseHeader( + HttpRequest httpRequest, HttpResponse httpResponse, String name) { + return httpResponse.getHeaders().getHeaderStringValues(name); + } } diff --git a/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlHttpAttributesExtractor.java b/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlHttpAttributesExtractor.java index 2ab540f0480e..b82f6a2fe27e 100644 --- a/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlHttpAttributesExtractor.java +++ b/instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlHttpAttributesExtractor.java @@ -5,13 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.httpurlconnection; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.HttpURLConnection; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; class HttpUrlHttpAttributesExtractor extends HttpClientAttributesExtractor { + + HttpUrlHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(HttpURLConnection connection) { return connection.getRequestMethod(); @@ -27,6 +37,12 @@ protected String url(HttpURLConnection connection) { return connection.getRequestProperty("User-Agent"); } + @Override + protected List requestHeader(HttpURLConnection connection, String name) { + String value = connection.getRequestProperty(name); + return value == null ? emptyList() : singletonList(value); + } + @Override protected @Nullable Long requestContentLength( HttpURLConnection connection, @Nullable Integer statusCode) { @@ -59,4 +75,10 @@ protected Integer statusCode(HttpURLConnection connection, Integer statusCode) { HttpURLConnection connection, Integer statusCode) { return null; } + + @Override + protected List responseHeader( + HttpURLConnection connection, Integer statusCode, String name) { + return emptyList(); + } } diff --git a/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpAttributesExtractor.java b/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpAttributesExtractor.java index 8faed023ab28..357609823474 100644 --- a/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpAttributesExtractor.java +++ b/instrumentation/java-http-client/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpclient/JdkHttpAttributesExtractor.java @@ -6,15 +6,21 @@ package io.opentelemetry.javaagent.instrumentation.httpclient; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.http.HttpClient.Version; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; class JdkHttpAttributesExtractor extends HttpClientAttributesExtractor> { + JdkHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(HttpRequest httpRequest) { return httpRequest.method(); @@ -30,6 +36,11 @@ protected String url(HttpRequest httpRequest) { return httpRequest.headers().firstValue("User-Agent").orElse(null); } + @Override + protected List requestHeader(HttpRequest httpRequest, String name) { + return httpRequest.headers().allValues(name); + } + @Override protected @Nullable Long requestContentLength( HttpRequest httpRequest, @Nullable HttpResponse httpResponse) { @@ -66,4 +77,10 @@ protected String flavor(HttpRequest httpRequest, @Nullable HttpResponse httpR HttpRequest httpRequest, HttpResponse httpResponse) { return null; } + + @Override + protected List responseHeader( + HttpRequest httpRequest, HttpResponse httpResponse, String name) { + return httpResponse.headers().allValues(name); + } } diff --git a/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientHttpAttributesExtractor.java b/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientHttpAttributesExtractor.java index ce2e2c267476..f6042c553aa1 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientHttpAttributesExtractor.java +++ b/instrumentation/jaxrs-client/jaxrs-client-1.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v1_1/JaxRsClientHttpAttributesExtractor.java @@ -5,15 +5,24 @@ package io.opentelemetry.javaagent.instrumentation.jaxrsclient.v1_1; +import static java.util.Collections.emptyList; + import com.sun.jersey.api.client.ClientRequest; import com.sun.jersey.api.client.ClientResponse; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.ArrayList; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class JaxRsClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + JaxRsClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected @Nullable String method(ClientRequest httpRequest) { return httpRequest.getMethod(); @@ -30,6 +39,20 @@ protected String url(ClientRequest httpRequest) { return header != null ? header.toString() : null; } + @Override + protected List requestHeader(ClientRequest httpRequest, String name) { + List rawHeaders = httpRequest.getHeaders().getOrDefault(name, emptyList()); + if (rawHeaders.isEmpty()) { + return emptyList(); + } + List stringHeaders = new ArrayList<>(rawHeaders.size()); + int i = 0; + for (Object headerValue : rawHeaders) { + stringHeaders.set(i++, String.valueOf(headerValue)); + } + return stringHeaders; + } + @Override protected @Nullable Long requestContentLength( ClientRequest httpRequest, @Nullable ClientResponse httpResponse) { @@ -64,4 +87,10 @@ protected String flavor(ClientRequest httpRequest, @Nullable ClientResponse http ClientRequest httpRequest, ClientResponse httpResponse) { return null; } + + @Override + protected List responseHeader( + ClientRequest httpRequest, ClientResponse httpResponse, String name) { + return httpResponse.getHeaders().getOrDefault(name, emptyList()); + } } diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/JaxRsClientHttpAttributesExtractor.java b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/JaxRsClientHttpAttributesExtractor.java index 9fe51741e43c..7c6ac1e19b39 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/JaxRsClientHttpAttributesExtractor.java +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/JaxRsClientHttpAttributesExtractor.java @@ -5,8 +5,12 @@ package io.opentelemetry.javaagent.instrumentation.jaxrsclient.v2_0; +import static java.util.Collections.emptyList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientResponseContext; import org.checkerframework.checker.nullness.qual.Nullable; @@ -14,6 +18,10 @@ final class JaxRsClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + JaxRsClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected @Nullable String method(ClientRequestContext httpRequest) { return httpRequest.getMethod(); @@ -29,6 +37,11 @@ protected String url(ClientRequestContext httpRequest) { return httpRequest.getHeaderString("User-Agent"); } + @Override + protected List requestHeader(ClientRequestContext httpRequest, String name) { + return httpRequest.getStringHeaders().getOrDefault(name, emptyList()); + } + @Override protected @Nullable Long requestContentLength( ClientRequestContext httpRequest, @Nullable ClientResponseContext httpResponse) { @@ -48,7 +61,7 @@ protected String flavor( } @Override - protected @Nullable Integer statusCode( + protected Integer statusCode( ClientRequestContext httpRequest, ClientResponseContext httpResponse) { return httpResponse.getStatus(); } @@ -65,4 +78,10 @@ protected String flavor( ClientRequestContext httpRequest, ClientResponseContext httpResponse) { return null; } + + @Override + protected List responseHeader( + ClientRequestContext httpRequest, ClientResponseContext httpResponse, String name) { + return httpResponse.getHeaders().getOrDefault(name, emptyList()); + } } diff --git a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/ResteasyClientHttpAttributesExtractor.java b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/ResteasyClientHttpAttributesExtractor.java index 226b8ce28057..50d65ee5eeb0 100644 --- a/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/ResteasyClientHttpAttributesExtractor.java +++ b/instrumentation/jaxrs-client/jaxrs-client-2.0/jaxrs-client-2.0-resteasy-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrsclient/v2_0/ResteasyClientHttpAttributesExtractor.java @@ -5,8 +5,13 @@ package io.opentelemetry.javaagent.instrumentation.jaxrsclient.v2_0; +import static java.util.Collections.emptyList; + import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.ArrayList; +import java.util.List; import javax.ws.rs.core.Response; import org.checkerframework.checker.nullness.qual.Nullable; import org.jboss.resteasy.client.jaxrs.internal.ClientInvocation; @@ -14,6 +19,10 @@ final class ResteasyClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + ResteasyClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected @Nullable String method(ClientInvocation httpRequest) { return httpRequest.getMethod(); @@ -29,6 +38,20 @@ protected String url(ClientInvocation httpRequest) { return httpRequest.getHeaders().getHeader("User-Agent"); } + @Override + protected List requestHeader(ClientInvocation httpRequest, String name) { + List rawHeaders = httpRequest.getHeaders().getHeaders().getOrDefault(name, emptyList()); + if (rawHeaders.isEmpty()) { + return emptyList(); + } + List stringHeaders = new ArrayList<>(rawHeaders.size()); + int i = 0; + for (Object headerValue : rawHeaders) { + stringHeaders.set(i++, String.valueOf(headerValue)); + } + return stringHeaders; + } + @Override protected @Nullable Long requestContentLength( ClientInvocation httpRequest, @Nullable Response httpResponse) { @@ -63,4 +86,10 @@ protected String flavor(ClientInvocation httpRequest, @Nullable Response httpRes ClientInvocation httpRequest, Response httpResponse) { return null; } + + @Override + protected List responseHeader( + ClientInvocation clientInvocation, Response response, String name) { + return response.getStringHeaders().getOrDefault(name, emptyList()); + } } diff --git a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientHttpAttributesExtractor.java b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientHttpAttributesExtractor.java index feedfd1f1d9a..8a373b3ebf28 100644 --- a/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientHttpAttributesExtractor.java +++ b/instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v9_2/internal/JettyClientHttpAttributesExtractor.java @@ -9,7 +9,9 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_1; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_2_0; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -24,6 +26,11 @@ final class JettyClientHttpAttributesExtractor private static final Logger logger = LoggerFactory.getLogger(JettyClientHttpAttributesExtractor.class); + // TODO: add support for capturing HTTP headers in library instrumentations + JettyClientHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override @Nullable protected String method(Request request) { @@ -43,6 +50,11 @@ protected String userAgent(Request request) { return agentField != null ? agentField.getValue() : null; } + @Override + protected List requestHeader(Request request, String name) { + return request.getHeaders().getValuesList(name); + } + @Override @Nullable protected Long requestContentLength(Request request, @Nullable Response response) { @@ -57,7 +69,6 @@ protected Long requestContentLengthUncompressed(Request request, @Nullable Respo } @Override - @Nullable protected String flavor(Request request, @Nullable Response response) { if (response == null) { @@ -82,7 +93,6 @@ protected String flavor(Request request, @Nullable Response response) { } @Override - @Nullable protected Integer statusCode(Request request, Response response) { return response.getStatus(); } @@ -105,6 +115,11 @@ protected Long responseContentLengthUncompressed(Request request, Response respo return null; } + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.getHeaders().getValuesList(name); + } + private static Long getLongFromJettyHttpField(HttpField httpField) { Long longFromField = null; try { diff --git a/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesHttpAttributesExtractor.java b/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesHttpAttributesExtractor.java index 60b61e32ee8d..28939f8d38e9 100644 --- a/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesHttpAttributesExtractor.java +++ b/instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesHttpAttributesExtractor.java @@ -5,14 +5,23 @@ package io.opentelemetry.javaagent.instrumentation.kubernetesclient; +import static java.util.Collections.emptyList; + import io.kubernetes.client.openapi.ApiResponse; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import okhttp3.Request; import org.checkerframework.checker.nullness.qual.Nullable; class KubernetesHttpAttributesExtractor extends HttpClientAttributesExtractor> { + + KubernetesHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(Request request) { return request.method(); @@ -28,6 +37,11 @@ protected String url(Request request) { return request.header("user-agent"); } + @Override + protected List requestHeader(Request request, String name) { + return request.headers(name); + } + @Override protected @Nullable Long requestContentLength( Request request, @Nullable ApiResponse apiResponse) { @@ -60,4 +74,9 @@ protected Integer statusCode(Request request, ApiResponse apiResponse) { Request request, ApiResponse apiResponse) { return null; } + + @Override + protected List responseHeader(Request request, ApiResponse apiResponse, String name) { + return apiResponse.getHeaders().getOrDefault(name, emptyList()); + } } diff --git a/instrumentation/liberty/compile-stub/src/main/java/com/ibm/ws/http/dispatcher/internal/channel/HttpDispatcherLink.java b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/ws/http/dispatcher/internal/channel/HttpDispatcherLink.java index 449d7d590164..4728d77e7b14 100644 --- a/instrumentation/liberty/compile-stub/src/main/java/com/ibm/ws/http/dispatcher/internal/channel/HttpDispatcherLink.java +++ b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/ws/http/dispatcher/internal/channel/HttpDispatcherLink.java @@ -5,6 +5,8 @@ package com.ibm.ws.http.dispatcher.internal.channel; +import com.ibm.wsspi.http.HttpResponse; + // https://github.com/OpenLiberty/open-liberty/blob/master/dev/com.ibm.ws.transport.http/src/com/ibm/ws/http/dispatcher/internal/channel/HttpDispatcherLink.java public class HttpDispatcherLink { @@ -27,4 +29,8 @@ public String getRequestedHost() { public int getRequestedPort() { throw new UnsupportedOperationException(); } + + public HttpResponse getResponse() { + throw new UnsupportedOperationException(); + } } diff --git a/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/HttpResponse.java b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/HttpResponse.java new file mode 100644 index 000000000000..c019100e5d1c --- /dev/null +++ b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/HttpResponse.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.wsspi.http; + +import java.util.List; + +// https://github.com/OpenLiberty/open-liberty/blob/integration/dev/com.ibm.ws.transport.http/src/com/ibm/wsspi/http/HttpResponse.java +public interface HttpResponse { + + List getHeaders(String name); +} diff --git a/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/channel/HttpRequestMessage.java b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/channel/HttpRequestMessage.java index 43f6af09b1ad..e4357701db9a 100644 --- a/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/channel/HttpRequestMessage.java +++ b/instrumentation/liberty/compile-stub/src/main/java/com/ibm/wsspi/http/channel/HttpRequestMessage.java @@ -15,6 +15,8 @@ public interface HttpRequestMessage { HeaderField getHeader(String paramString); + List getHeaders(String name); + String getScheme(); String getRequestURI(); diff --git a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherHttpAttributesExtractor.java b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherHttpAttributesExtractor.java index 8d44114e0b7e..d03f28501abb 100644 --- a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherHttpAttributesExtractor.java +++ b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherHttpAttributesExtractor.java @@ -6,14 +6,16 @@ package io.opentelemetry.javaagent.instrumentation.liberty.dispatcher; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class LibertyDispatcherHttpAttributesExtractor extends HttpServerAttributesExtractor { - private static final Logger logger = - LoggerFactory.getLogger(LibertyDispatcherHttpAttributesExtractor.class); + + public LibertyDispatcherHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedServerHeaders()); + } @Override protected @Nullable String method(LibertyRequest libertyRequest) { @@ -25,6 +27,11 @@ public class LibertyDispatcherHttpAttributesExtractor return libertyRequest.getHeaderValue("User-Agent"); } + @Override + protected List requestHeader(LibertyRequest libertyRequest, String name) { + return libertyRequest.getHeaderValues(name); + } + @Override protected @Nullable Long requestContentLength( LibertyRequest libertyRequest, @Nullable LibertyResponse libertyResponse) { @@ -67,6 +74,12 @@ public class LibertyDispatcherHttpAttributesExtractor return null; } + @Override + protected List responseHeader( + LibertyRequest libertyRequest, LibertyResponse libertyResponse, String name) { + return libertyResponse.getHeaderValues(name); + } + @Override protected @Nullable String target(LibertyRequest libertyRequest) { String requestUri = libertyRequest.getRequestUri(); diff --git a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherLinkInstrumentation.java b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherLinkInstrumentation.java index acf7d195372c..7103e3eaa567 100644 --- a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherLinkInstrumentation.java +++ b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyDispatcherLinkInstrumentation.java @@ -68,6 +68,7 @@ public static void onEnter( @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) public static void stopSpan( + @Advice.This HttpDispatcherLink httpDispatcherLink, @Advice.Thrown Throwable throwable, @Advice.Argument(value = 0) StatusCodes statusCode, @Advice.Argument(value = 2) Exception failure, @@ -79,7 +80,7 @@ public static void stopSpan( } scope.close(); - LibertyResponse response = new LibertyResponse(statusCode); + LibertyResponse response = new LibertyResponse(httpDispatcherLink, statusCode); request.setCompleted(); Throwable t = failure != null ? failure : throwable; diff --git a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyRequest.java b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyRequest.java index 549173c7b4e6..4a58524ebbab 100644 --- a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyRequest.java +++ b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyRequest.java @@ -8,6 +8,8 @@ import com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink; import com.ibm.wsspi.genericbnf.HeaderField; import com.ibm.wsspi.http.channel.HttpRequestMessage; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class LibertyRequest { @@ -54,6 +56,19 @@ public String getHeaderValue(String name) { return hf != null ? hf.asString() : null; } + public List getHeaderValues(String name) { + List headers = httpRequestMessage.getHeaders(name); + if (headers.isEmpty()) { + return Collections.emptyList(); + } + List stringHeaders = new ArrayList<>(headers.size()); + int i = 0; + for (HeaderField header : headers) { + stringHeaders.set(i++, header.asString()); + } + return stringHeaders; + } + public int peerPort() { return httpDispatcherLink.getRemotePort(); } diff --git a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyResponse.java b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyResponse.java index 8d58d0c61f64..d449dde88e14 100644 --- a/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyResponse.java +++ b/instrumentation/liberty/liberty-dispatcher/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/liberty/dispatcher/LibertyResponse.java @@ -5,16 +5,29 @@ package io.opentelemetry.javaagent.instrumentation.liberty.dispatcher; +import com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink; +import com.ibm.wsspi.http.HttpResponse; import com.ibm.wsspi.http.channel.values.StatusCodes; +import java.util.Collections; +import java.util.List; public class LibertyResponse { + private final HttpDispatcherLink httpDispatcherLink; private final StatusCodes code; - public LibertyResponse(StatusCodes code) { + public LibertyResponse(HttpDispatcherLink httpDispatcherLink, StatusCodes code) { + this.httpDispatcherLink = httpDispatcherLink; this.code = code; } public int getStatus() { return code.getIntCode(); } + + public List getHeaderValues(String name) { + HttpResponse response = httpDispatcherLink.getResponse(); + // response is set to null on destroy(), so it shouldn't really ever be null in the middle of + // request processing, but just to be safe let's check it + return response == null ? Collections.emptyList() : response.getHeaders(name); + } } diff --git a/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2HttpAttributesExtractor.java b/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2HttpAttributesExtractor.java index 67cdfd45941a..8ffb45b25a53 100644 --- a/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2HttpAttributesExtractor.java +++ b/instrumentation/okhttp/okhttp-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/okhttp/v2_2/OkHttp2HttpAttributesExtractor.java @@ -8,12 +8,18 @@ import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; final class OkHttp2HttpAttributesExtractor extends HttpClientAttributesExtractor { + OkHttp2HttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(Request request) { return request.method(); @@ -30,6 +36,11 @@ protected String userAgent(Request request) { return request.header("User-Agent"); } + @Override + protected List requestHeader(Request request, String name) { + return request.headers(name); + } + @Override @Nullable protected Long requestContentLength(Request request, @Nullable Response response) { @@ -78,4 +89,9 @@ protected Long responseContentLength(Request request, Response response) { protected Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.headers(name); + } } diff --git a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpAttributesExtractor.java b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpAttributesExtractor.java index cbb923b9d6d4..b53fd6c78ac9 100644 --- a/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpAttributesExtractor.java +++ b/instrumentation/okhttp/okhttp-3.0/library/src/main/java/io/opentelemetry/instrumentation/okhttp/v3_0/OkHttpAttributesExtractor.java @@ -5,13 +5,21 @@ package io.opentelemetry.instrumentation.okhttp.v3_0; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import okhttp3.Request; import okhttp3.Response; import org.checkerframework.checker.nullness.qual.Nullable; final class OkHttpAttributesExtractor extends HttpClientAttributesExtractor { + + // TODO: add support for capturing HTTP headers in library instrumentations + OkHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(Request request) { return request.method(); @@ -28,6 +36,11 @@ protected String url(Request request) { return request.header("user-agent"); } + @Override + protected List requestHeader(Request request, String name) { + return request.headers(name); + } + @Override protected @Nullable Long requestContentLength(Request request, @Nullable Response response) { return null; @@ -74,4 +87,9 @@ protected Integer statusCode(Request request, Response response) { protected @Nullable Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.headers(name); + } } diff --git a/instrumentation/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientHttpAttributesExtractor.java b/instrumentation/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientHttpAttributesExtractor.java index 5dbcb65d01fe..c8a8b07ff406 100644 --- a/instrumentation/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientHttpAttributesExtractor.java +++ b/instrumentation/play-ws/play-ws-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/playws/PlayWsClientHttpAttributesExtractor.java @@ -6,7 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.playws; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import play.shaded.ahc.org.asynchttpclient.Request; import play.shaded.ahc.org.asynchttpclient.Response; @@ -14,6 +16,10 @@ final class PlayWsClientHttpAttributesExtractor extends HttpClientAttributesExtractor { + PlayWsClientHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedClientHeaders()); + } + @Override protected String method(Request request) { return request.getMethod(); @@ -30,6 +36,11 @@ protected String userAgent(Request request) { return null; } + @Override + protected List requestHeader(Request request, String name) { + return request.getHeaders().getAll(name); + } + @Override @Nullable protected Long requestContentLength(Request request, @Nullable Response response) { @@ -63,4 +74,9 @@ protected Long responseContentLength(Request request, Response response) { protected Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.getHeaders().getAll(name); + } } diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java index f21a48dd8f3b..a8d306cc2623 100644 --- a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java @@ -5,9 +5,11 @@ package io.opentelemetry.instrumentation.ratpack; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.URI; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import ratpack.handling.Context; import ratpack.http.Request; @@ -16,6 +18,12 @@ final class RatpackHttpAttributesExtractor extends HttpServerAttributesExtractor { + + // TODO: add support for capturing HTTP headers in library instrumentations + RatpackHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(Request request) { return request.getMethod().getName(); @@ -69,6 +77,11 @@ protected String userAgent(Request request) { return request.getHeaders().get("user-agent"); } + @Override + protected List requestHeader(Request request, String name) { + return request.getHeaders().getAll(name); + } + @Override @Nullable protected Long requestContentLength(Request request, @Nullable Response response) { @@ -119,4 +132,9 @@ protected Long responseContentLength(Request request, Response response) { protected Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return response.getHeaders().getAll(name); + } } diff --git a/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHeadersGetter.java b/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHeadersGetter.java index 2df5e3d7503d..2847b7fcc789 100644 --- a/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHeadersGetter.java +++ b/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHeadersGetter.java @@ -8,6 +8,7 @@ import io.opentelemetry.context.propagation.TextMapGetter; import java.util.Locale; import org.restlet.data.Form; +import org.restlet.data.Message; import org.restlet.data.Request; final class RestletHeadersGetter implements TextMapGetter { @@ -29,7 +30,7 @@ public String get(Request carrier, String key) { return headers.getFirstValue(key.toLowerCase(Locale.ROOT)); } - private static Form getHeaders(Request carrier) { + static Form getHeaders(Message carrier) { return (Form) carrier.getAttributes().get("org.restlet.http.headers"); } } diff --git a/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHttpAttributesExtractor.java b/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHttpAttributesExtractor.java index 6e9020d676f8..304107bd1cae 100644 --- a/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHttpAttributesExtractor.java +++ b/instrumentation/restlet/restlet-1.0/library/src/main/java/io/opentelemetry/instrumentation/restlet/v1_0/RestletHttpAttributesExtractor.java @@ -5,16 +5,29 @@ package io.opentelemetry.instrumentation.restlet.v1_0; +import static io.opentelemetry.instrumentation.restlet.v1_0.RestletHeadersGetter.getHeaders; + +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import org.restlet.data.Parameter; import org.restlet.data.Reference; import org.restlet.data.Request; import org.restlet.data.Response; +import org.restlet.util.Series; final class RestletHttpAttributesExtractor extends HttpServerAttributesExtractor { + // TODO: add support for capturing HTTP headers in library instrumentations + RestletHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(Request request) { return request.getMethod().toString(); @@ -48,6 +61,11 @@ protected String host(Request request) { return request.getClientInfo().getAgent(); } + @Override + protected List requestHeader(Request request, String name) { + return parametersToList(getHeaders(request).subList(name, /* ignoreCase = */ true)); + } + @Override protected @Nullable Long requestContentLength(Request request, @Nullable Response response) { return null; @@ -94,4 +112,22 @@ protected Integer statusCode(Request request, Response response) { protected @Nullable Long responseContentLengthUncompressed(Request request, Response response) { return null; } + + @Override + protected List responseHeader(Request request, Response response, String name) { + return parametersToList(getHeaders(response).subList(name, /* ignoreCase = */ true)); + } + + // minimize memory overhead by not using streams + private static List parametersToList(Series headers) { + if (headers.isEmpty()) { + return Collections.emptyList(); + } + List stringHeaders = new ArrayList<>(headers.size()); + int i = 0; + for (Parameter header : headers) { + stringHeaders.set(i++, header.getValue()); + } + return stringHeaders; + } } diff --git a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java index a30f0714c139..2f3413bc0d3b 100644 --- a/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java +++ b/instrumentation/servlet/servlet-2.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v2_2/Servlet2Accessor.java @@ -7,6 +7,8 @@ import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; import io.opentelemetry.instrumentation.servlet.javax.JavaxServletAccessor; +import java.util.Collections; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -38,6 +40,12 @@ public String getResponseHeader(HttpServletResponse httpServletResponse, String return null; } + @Override + public List getResponseHeaderValues( + HttpServletResponse httpServletResponse, String name) { + return Collections.emptyList(); + } + @Override public boolean isResponseCommitted(HttpServletResponse httpServletResponse) { return httpServletResponse.isCommitted(); diff --git a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Accessor.java b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Accessor.java index 6cc854cb25ee..9aa46bc56f59 100644 --- a/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Accessor.java +++ b/instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3Accessor.java @@ -7,6 +7,10 @@ import io.opentelemetry.instrumentation.servlet.ServletAsyncListener; import io.opentelemetry.instrumentation.servlet.javax.JavaxServletAccessor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.http.HttpServletRequest; @@ -44,6 +48,18 @@ public String getResponseHeader(HttpServletResponse response, String name) { return response.getHeader(name); } + @Override + public List getResponseHeaderValues(HttpServletResponse response, String name) { + Collection values = response.getHeaders(name); + if (values == null) { + return Collections.emptyList(); + } + if (values instanceof List) { + return (List) values; + } + return new ArrayList<>(values); + } + @Override public boolean isResponseCommitted(HttpServletResponse response) { return response.isCommitted(); diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java index 677cddccc516..100112c97179 100644 --- a/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java +++ b/instrumentation/servlet/servlet-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v5_0/Servlet5Accessor.java @@ -13,7 +13,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; +import java.util.List; public class Servlet5Accessor implements ServletAccessor { public static final Servlet5Accessor INSTANCE = new Servlet5Accessor(); @@ -80,6 +84,12 @@ public String getRequestHeader(HttpServletRequest request, String name) { return request.getHeader(name); } + @Override + public List getRequestHeaderValues(HttpServletRequest request, String name) { + Enumeration values = request.getHeaders(name); + return values == null ? Collections.emptyList() : Collections.list(values); + } + @Override public Iterable getRequestHeaderNames(HttpServletRequest httpServletRequest) { return Collections.list(httpServletRequest.getHeaderNames()); @@ -137,6 +147,18 @@ public String getResponseHeader(HttpServletResponse response, String name) { return response.getHeader(name); } + @Override + public List getResponseHeaderValues(HttpServletResponse response, String name) { + Collection values = response.getHeaders(name); + if (values == null) { + return Collections.emptyList(); + } + if (values instanceof List) { + return (List) values; + } + return new ArrayList<>(values); + } + @Override public boolean isResponseCommitted(HttpServletResponse response) { return response.isCommitted(); diff --git a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java index 03d2c805a7f3..c43498b30327 100644 --- a/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java +++ b/instrumentation/servlet/servlet-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/ServletHttpAttributesExtractor.java @@ -7,6 +7,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.instrumentation.servlet.ServletAccessor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public class ServletHttpAttributesExtractor @@ -15,6 +17,7 @@ public class ServletHttpAttributesExtractor protected final ServletAccessor accessor; public ServletHttpAttributesExtractor(ServletAccessor accessor) { + super(HttpHeadersConfig.capturedServerHeaders()); this.accessor = accessor; } @@ -50,6 +53,11 @@ public ServletHttpAttributesExtractor(ServletAccessor accesso return accessor.getRequestHeader(requestContext.request(), "User-Agent"); } + @Override + protected List requestHeader(ServletRequestContext requestContext, String name) { + return accessor.getRequestHeaderValues(requestContext.request(), name); + } + @Override protected @Nullable Long requestContentLength( ServletRequestContext requestContext, @@ -123,6 +131,14 @@ public ServletHttpAttributesExtractor(ServletAccessor accesso return null; } + @Override + protected List responseHeader( + ServletRequestContext requestContext, + ServletResponseContext responseContext, + String name) { + return accessor.getResponseHeaderValues(responseContext.response(), name); + } + @Override protected @Nullable String route(ServletRequestContext requestContext) { return null; diff --git a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java index 87ce609b62f0..c44fed922550 100644 --- a/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java +++ b/instrumentation/servlet/servlet-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/ServletAccessor.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.servlet; import java.security.Principal; +import java.util.List; /** * This interface is used to access methods of ServletContext, HttpServletRequest and @@ -42,6 +43,8 @@ public interface ServletAccessor { String getRequestHeader(REQUEST request, String name); + List getRequestHeaderValues(REQUEST request, String name); + Iterable getRequestHeaderNames(REQUEST request); String getRequestServletPath(REQUEST request); @@ -63,6 +66,8 @@ void addRequestAsyncListener( String getResponseHeader(RESPONSE response, String name); + List getResponseHeaderValues(RESPONSE response, String name); + boolean isResponseCommitted(RESPONSE response); boolean isServletException(Throwable throwable); diff --git a/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java b/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java index c92253ce8835..0ef178e4829d 100644 --- a/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java +++ b/instrumentation/servlet/servlet-javax-common/library/src/main/java/io/opentelemetry/instrumentation/servlet/javax/JavaxServletAccessor.java @@ -8,6 +8,8 @@ import io.opentelemetry.instrumentation.servlet.ServletAccessor; import java.security.Principal; import java.util.Collections; +import java.util.Enumeration; +import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -77,6 +79,12 @@ public String getRequestHeader(HttpServletRequest request, String name) { return request.getHeader(name); } + @Override + public List getRequestHeaderValues(HttpServletRequest request, String name) { + Enumeration values = request.getHeaders(name); + return values == null ? Collections.emptyList() : Collections.list(values); + } + @Override public Iterable getRequestHeaderNames(HttpServletRequest httpServletRequest) { return Collections.list(httpServletRequest.getHeaderNames()); diff --git a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java index beebd7490c01..d6b5f2474bc8 100644 --- a/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java +++ b/instrumentation/spring/spring-web-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/web/SpringWebHttpAttributesExtractor.java @@ -5,8 +5,12 @@ package io.opentelemetry.instrumentation.spring.web; +import static java.util.Collections.emptyList; + +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import java.io.IOException; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; @@ -14,6 +18,12 @@ final class SpringWebHttpAttributesExtractor extends HttpClientAttributesExtractor { + + // TODO: add support for capturing HTTP headers in library instrumentations + SpringWebHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected String method(HttpRequest httpRequest) { return httpRequest.getMethod().name(); @@ -30,6 +40,11 @@ protected String method(HttpRequest httpRequest) { return httpRequest.getHeaders().getFirst("user-agent"); } + @Override + protected List requestHeader(HttpRequest httpRequest, String name) { + return httpRequest.getHeaders().getOrDefault(name, emptyList()); + } + @Override protected @Nullable Long requestContentLength( HttpRequest httpRequest, @Nullable ClientHttpResponse clientHttpResponse) { @@ -68,4 +83,10 @@ protected Integer statusCode(HttpRequest httpRequest, ClientHttpResponse clientH HttpRequest httpRequest, ClientHttpResponse clientHttpResponse) { return null; } + + @Override + protected List responseHeader( + HttpRequest httpRequest, ClientHttpResponse clientHttpResponse, String name) { + return clientHttpResponse.getHeaders().getOrDefault(name, emptyList()); + } } diff --git a/instrumentation/spring/spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/SpringWebMvcHttpAttributesExtractor.java b/instrumentation/spring/spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/SpringWebMvcHttpAttributesExtractor.java index c4776fa0a6ae..44cd00fee0a3 100644 --- a/instrumentation/spring/spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/SpringWebMvcHttpAttributesExtractor.java +++ b/instrumentation/spring/spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/SpringWebMvcHttpAttributesExtractor.java @@ -5,13 +5,25 @@ package io.opentelemetry.instrumentation.spring.webmvc; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.checkerframework.checker.nullness.qual.Nullable; final class SpringWebMvcHttpAttributesExtractor extends HttpServerAttributesExtractor { + + // TODO: add support for capturing HTTP headers in library instrumentations + SpringWebMvcHttpAttributesExtractor() { + super(CapturedHttpHeaders.empty()); + } + @Override protected @Nullable String method(HttpServletRequest request) { return request.getMethod(); @@ -22,6 +34,12 @@ final class SpringWebMvcHttpAttributesExtractor return request.getHeader("user-agent"); } + @Override + protected List requestHeader(HttpServletRequest request, String name) { + Enumeration headers = request.getHeaders(name); + return headers == null ? Collections.emptyList() : Collections.list(headers); + } + @Override protected @Nullable Long requestContentLength( HttpServletRequest request, @Nullable HttpServletResponse response) { @@ -57,6 +75,19 @@ final class SpringWebMvcHttpAttributesExtractor return null; } + @Override + protected List responseHeader( + HttpServletRequest request, HttpServletResponse response, String name) { + Collection headers = response.getHeaders(name); + if (headers == null) { + return Collections.emptyList(); + } + if (headers instanceof List) { + return (List) headers; + } + return new ArrayList<>(headers); + } + @Override protected @Nullable String target(HttpServletRequest request) { String target = request.getRequestURI(); diff --git a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java index 386be84e62ab..5b8896e45003 100644 --- a/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java +++ b/instrumentation/tomcat/tomcat-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/tomcat/common/TomcatHttpAttributesExtractor.java @@ -6,6 +6,9 @@ package io.opentelemetry.javaagent.instrumentation.tomcat.common; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; +import java.util.Collections; +import java.util.List; import org.apache.coyote.Request; import org.apache.coyote.Response; import org.apache.tomcat.util.buf.MessageBytes; @@ -14,6 +17,10 @@ public class TomcatHttpAttributesExtractor extends HttpServerAttributesExtractor { + public TomcatHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedServerHeaders()); + } + @Override protected String method(Request request) { return request.method().toString(); @@ -45,6 +52,11 @@ protected String method(Request request) { return request.getHeader("User-Agent"); } + @Override + protected List requestHeader(Request request, String name) { + return Collections.list(request.getMimeHeaders().values(name)); + } + @Override protected @Nullable Long requestContentLength(Request request, @Nullable Response response) { /* @@ -91,6 +103,11 @@ protected String method(Request request) { return null; } + @Override + protected List responseHeader(Request request, Response response, String name) { + return Collections.list(response.getMimeHeaders().values(name)); + } + @Override protected @Nullable String route(Request request) { return null; diff --git a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpAttributesExtractor.java b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpAttributesExtractor.java index 82f8549df2a7..902139027db9 100644 --- a/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpAttributesExtractor.java +++ b/instrumentation/undertow-1.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/undertow/UndertowHttpAttributesExtractor.java @@ -6,12 +6,20 @@ package io.opentelemetry.javaagent.instrumentation.undertow; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; +import io.opentelemetry.javaagent.instrumentation.api.config.HttpHeadersConfig; import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderValues; +import java.util.Collections; +import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; public class UndertowHttpAttributesExtractor extends HttpServerAttributesExtractor { + public UndertowHttpAttributesExtractor() { + super(HttpHeadersConfig.capturedServerHeaders()); + } + @Override protected String method(HttpServerExchange exchange) { return exchange.getRequestMethod().toString(); @@ -22,6 +30,12 @@ protected String method(HttpServerExchange exchange) { return exchange.getRequestHeaders().getFirst("User-Agent"); } + @Override + protected List requestHeader(HttpServerExchange exchange, String name) { + HeaderValues values = exchange.getRequestHeaders().get(name); + return values == null ? Collections.emptyList() : values; + } + @Override protected @Nullable Long requestContentLength( HttpServerExchange exchange, @Nullable HttpServerExchange unused) { @@ -63,6 +77,13 @@ protected Integer statusCode(HttpServerExchange exchange, HttpServerExchange unu return null; } + @Override + protected List responseHeader( + HttpServerExchange exchange, HttpServerExchange unused, String name) { + HeaderValues values = exchange.getResponseHeaders().get(name); + return values == null ? Collections.emptyList() : values; + } + @Override protected @Nullable String target(HttpServerExchange exchange) { String requestPath = exchange.getRequestPath(); diff --git a/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/config/HttpHeadersConfig.java b/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/config/HttpHeadersConfig.java new file mode 100644 index 000000000000..ca5556b091fb --- /dev/null +++ b/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/config/HttpHeadersConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.api.config; + +import io.opentelemetry.instrumentation.api.config.Config; +import io.opentelemetry.instrumentation.api.instrumenter.http.CapturedHttpHeaders; + +/** Javaagent configuration of captured HTTP request/response headers. */ +public final class HttpHeadersConfig { + + private static final CapturedHttpHeaders CLIENT; + private static final CapturedHttpHeaders SERVER; + + static { + Config config = Config.get(); + CLIENT = + CapturedHttpHeaders.create( + config.getList( + "otel.instrumentation.common.experimental.capture-http-headers.client.request"), + config.getList( + "otel.instrumentation.common.experimental.capture-http-headers.client.response")); + SERVER = + CapturedHttpHeaders.create( + config.getList( + "otel.instrumentation.common.experimental.capture-http-headers.server.request"), + config.getList( + "otel.instrumentation.common.experimental.capture-http-headers.server.response")); + } + + public static CapturedHttpHeaders capturedClientHeaders() { + return CLIENT; + } + + public static CapturedHttpHeaders capturedServerHeaders() { + return SERVER; + } + + private HttpHeadersConfig() {} +}