diff --git a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequestGetter.java b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequestGetter.java index aa798fcad00e..4a2ba08ee36f 100644 --- a/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequestGetter.java +++ b/instrumentation/grpc-1.6/library/src/main/java/io/opentelemetry/instrumentation/grpc/v1_6/GrpcRequestGetter.java @@ -10,6 +10,7 @@ import io.grpc.Metadata; import io.opentelemetry.context.propagation.TextMapGetter; import java.util.Iterator; +import java.util.stream.Collectors; import javax.annotation.Nullable; enum GrpcRequestGetter implements TextMapGetter { @@ -17,7 +18,11 @@ enum GrpcRequestGetter implements TextMapGetter { @Override public Iterable keys(GrpcRequest request) { - return request.getMetadata().keys(); + // Filter out HTTP/2 pseudo-headers (starting with ':') as they cannot be + // accessed via Metadata.Key.of() and would cause IllegalArgumentException + return request.getMetadata().keys().stream() + .filter(key -> !key.startsWith(":")) + .collect(Collectors.toList()); } @Override diff --git a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/test/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxTextMapGetterTest.java b/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/test/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxTextMapGetterTest.java deleted file mode 100644 index e0bbb1cc79ba..000000000000 --- a/instrumentation/spring/spring-webflux/spring-webflux-5.3/library/src/test/java/io/opentelemetry/instrumentation/spring/webflux/v5_3/WebfluxTextMapGetterTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.webflux.v5_3; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -class WebfluxTextMapGetterTest { - - @Test - void testGet() { - MockServerHttpRequest request = - MockServerHttpRequest.get("/test") - .header("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - .header("custom-header", "custom-value") - .build(); - - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - String traceparent = WebfluxTextMapGetter.INSTANCE.get(exchange, "traceparent"); - assertThat(traceparent).isEqualTo("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"); - - String customHeader = WebfluxTextMapGetter.INSTANCE.get(exchange, "custom-header"); - assertThat(customHeader).isEqualTo("custom-value"); - } - - @Test - void testGetAll() { - MockServerHttpRequest request = - MockServerHttpRequest.get("/test") - .header("accept", "application/json") - .header("accept", "text/html") - .build(); - - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - List acceptHeaders = new ArrayList<>(); - WebfluxTextMapGetter.INSTANCE.getAll(exchange, "accept").forEachRemaining(acceptHeaders::add); - - assertThat(acceptHeaders).containsExactly("application/json", "text/html"); - } - - @Test - void testKeysWithBaggageHeader() { - MockServerHttpRequest request = - MockServerHttpRequest.get("/test") - .header("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - .header("baggage", "test-baggage-key-1=test-baggage-value-1") - .build(); - - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - Iterable keys = WebfluxTextMapGetter.INSTANCE.keys(exchange); - assertThat(keys).contains("traceparent", "baggage"); - - String baggageValue = WebfluxTextMapGetter.INSTANCE.get(exchange, "baggage"); - assertThat(baggageValue).isEqualTo("test-baggage-key-1=test-baggage-value-1"); - } - - @Test - void testKeysWithMultipleBaggageHeaders() { - MockServerHttpRequest request = - MockServerHttpRequest.get("/test") - .header("traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - .header("baggage", "test-baggage-key-1=test-baggage-value-1") - .header("baggage", "test-baggage-key-2=test-baggage-value-2") - .header("x-custom", "custom-value") - .build(); - - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - Iterable keys = WebfluxTextMapGetter.INSTANCE.keys(exchange); - assertThat(keys).contains("traceparent", "baggage", "x-custom"); - - List baggageValues = new ArrayList<>(); - WebfluxTextMapGetter.INSTANCE.getAll(exchange, "baggage").forEachRemaining(baggageValues::add); - - assertThat(baggageValues) - .containsExactly( - "test-baggage-key-1=test-baggage-value-1", "test-baggage-key-2=test-baggage-value-2"); - } -} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java index e5ad63bf2f85..fd7b08cbdb05 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/LibraryTestRunner.java @@ -25,6 +25,7 @@ import io.opentelemetry.instrumentation.testing.provider.TestLogRecordExporterComponentProvider; import io.opentelemetry.instrumentation.testing.provider.TestMetricExporterComponentProvider; import io.opentelemetry.instrumentation.testing.provider.TestSpanExporterComponentProvider; +import io.opentelemetry.instrumentation.testing.util.KeysVerifyingPropagator; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; @@ -103,6 +104,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner { .setPropagators( ContextPropagators.create( TextMapPropagator.composite( + KeysVerifyingPropagator.getInstance(), W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()))) .buildAndRegisterGlobal(); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/KeysVerifyingPropagator.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/KeysVerifyingPropagator.java new file mode 100644 index 000000000000..7649b73d9430 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/util/KeysVerifyingPropagator.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.util; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Collection; +import javax.annotation.Nullable; + +/** + * A TextMapPropagator that verifies TextMapGetter methods can be called without errors. + * + *

This propagator performs verification during extraction but doesn't extract any context + * itself. Its purpose is to catch compatibility issues where underlying library APIs change (e.g., + * NoSuchMethodError). + */ +public final class KeysVerifyingPropagator implements TextMapPropagator { + private static final KeysVerifyingPropagator INSTANCE = new KeysVerifyingPropagator(); + + public static KeysVerifyingPropagator getInstance() { + return INSTANCE; + } + + private KeysVerifyingPropagator() {} + + @Override + public Collection fields() { + return emptyList(); + } + + @Override + public void inject(Context context, C carrier, TextMapSetter setter) { + // This propagator doesn't inject anything + } + + @Override + @SuppressWarnings("OtelCanIgnoreReturnValueSuggester") + public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) { + // Exercise methods to verify no errors + Iterable keys = getter.keys(carrier); + if (keys != null) { + keys.iterator().forEachRemaining(key -> getter.get(carrier, key)); + } + + return context; + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterConfigSupplier.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterConfigSupplier.java index ee40fe02a701..e6541fa8b7c6 100644 --- a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterConfigSupplier.java +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/exporter/AgentTestingExporterConfigSupplier.java @@ -24,6 +24,7 @@ private static Map getTestProperties() { properties.put("otel.logs.exporter", "none"); properties.put("otel.metrics.exporter", "none"); properties.put("otel.traces.exporter", "none"); + properties.put("otel.propagators", "testing-verifying-propagator"); return properties; } } diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/propagator/KeysVerifyingPropagatorProvider.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/propagator/KeysVerifyingPropagatorProvider.java new file mode 100644 index 000000000000..2e06372236c1 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/propagator/KeysVerifyingPropagatorProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.propagator; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.javaagent.testing.util.KeysVerifyingPropagator; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider; + +/** Provides a composite propagator that includes the default W3C propagators plus verification. */ +@AutoService(ConfigurablePropagatorProvider.class) +public final class KeysVerifyingPropagatorProvider implements ConfigurablePropagatorProvider { + @Override + public TextMapPropagator getPropagator(ConfigProperties configProperties) { + return TextMapPropagator.composite( + KeysVerifyingPropagator.getInstance(), + W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance()); + } + + @Override + public String getName() { + return "testing-verifying-propagator"; + } +} diff --git a/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/util/KeysVerifyingPropagator.java b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/util/KeysVerifyingPropagator.java new file mode 100644 index 000000000000..75d8504322a4 --- /dev/null +++ b/testing/agent-exporter/src/main/java/io/opentelemetry/javaagent/testing/util/KeysVerifyingPropagator.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.testing.util; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Collection; +import javax.annotation.Nullable; + +/** + * A TextMapPropagator that verifies TextMapGetter methods can be called without errors. + * + *

This propagator performs verification during extraction but doesn't extract any context + * itself. Its purpose is to catch compatibility issues where underlying library APIs change (e.g., + * NoSuchMethodError). + * + *

Note: This is a copy of the class in testing-common to avoid dependency conflicts + */ +public final class KeysVerifyingPropagator implements TextMapPropagator { + private static final KeysVerifyingPropagator INSTANCE = new KeysVerifyingPropagator(); + + public static KeysVerifyingPropagator getInstance() { + return INSTANCE; + } + + private KeysVerifyingPropagator() {} + + @Override + public Collection fields() { + return emptyList(); + } + + @Override + public void inject(Context context, C carrier, TextMapSetter setter) { + // This propagator doesn't inject anything + } + + @Override + @SuppressWarnings("OtelCanIgnoreReturnValueSuggester") + public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) { + // Exercise methods to verify no errors + Iterable keys = getter.keys(carrier); + if (keys != null) { + keys.iterator().forEachRemaining(key -> getter.get(carrier, key)); + } + + return context; + } +}