diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterUtil.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterUtil.java index 151862501bc8..05e52f574dbb 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterUtil.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpExporterUtil.java @@ -6,7 +6,10 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp; import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.time.Duration; +import java.util.AbstractMap; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; @@ -79,10 +82,20 @@ static E applySignalProperties( headers = properties.getHeaders(); } for (Map.Entry entry : headers.entrySet()) { - if (isHttpProtobuf) { - addHttpHeader.accept(httpBuilder, entry); - } else { - addGrpcHeader.accept(grpcBuilder, entry); + String value = entry.getValue(); + try { + // headers are encoded as URL - see + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables + Map.Entry decoded = + new AbstractMap.SimpleEntry<>(entry.getKey(), URLDecoder.decode(value, "UTF-8")); + + if (isHttpProtobuf) { + addHttpHeader.accept(httpBuilder, decoded); + } else { + addGrpcHeader.accept(grpcBuilder, decoded); + } + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("cannot decode header value: " + value, e); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java index 54449e437cbc..8cd7d13a52ee 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/exporters/otlp/OtlpSpanExporterAutoConfigurationTest.java @@ -118,12 +118,12 @@ void useHttpWithEnv() { .withPropertyValues( "otel.exporter.otlp.enabled=true", "otel.exporter.otlp.protocol=http/protobuf") // are similar to environment variables in that they use the same converters - .withSystemProperties("otel.exporter.otlp.headers=x=1,y=2") + .withSystemProperties("otel.exporter.otlp.headers=x=1,y=2%203") .run(context -> {}); Mockito.verify(otlpHttpSpanExporterBuilder).build(); Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("x", "1"); - Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("y", "2"); + Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("y", "2 3"); Mockito.verifyNoMoreInteractions(otlpHttpSpanExporterBuilder); }