From df9a72bbb06620fb3834520e1b7dcc06a436c0b9 Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Thu, 12 Feb 2026 18:18:04 +0530 Subject: [PATCH 1/6] Implement Environment Variable Context Propagation carriers in api/incubator --- .../propagation/EnvironmentGetter.java | 47 +++++++++++++++++ .../propagation/EnvironmentSetter.java | 37 ++++++++++++++ .../propagation/EnvironmentGetterTest.java | 51 +++++++++++++++++++ .../propagation/EnvironmentSetterTest.java | 41 +++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java create mode 100644 api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java new file mode 100644 index 00000000000..5dfae10392f --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.propagation; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * A {@link TextMapGetter} that extracts context from a map carrier, intended for use with + * environment variables. + * + *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code + * TRACESTATE}, {@code BAGGAGE}). This getter translates keys to uppercase before looking them up in + * the carrier. + */ +public enum EnvironmentGetter implements TextMapGetter> { + INSTANCE; + + @Override + public Iterable keys(Map carrier) { + if (carrier == null) { + return Collections.emptyList(); + } + return carrier.keySet(); + } + + @Nullable + @Override + public String get(@Nullable Map carrier, String key) { + if (carrier == null || key == null) { + return null; + } + // Spec recommends using uppercase for environment variable names. + return carrier.get(key.toUpperCase(Locale.ROOT)); + } + + @Override + public String toString() { + return "EnvironmentGetter"; + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java new file mode 100644 index 00000000000..bc94a12af23 --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.propagation; + +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Locale; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * A {@link TextMapSetter} that injects context into a map carrier, intended for use with + * environment variables. + * + *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code + * TRACESTATE}, {@code BAGGAGE}). This setter translates keys to uppercase before inserting them + * into the carrier. + */ +public enum EnvironmentSetter implements TextMapSetter> { + INSTANCE; + + @Override + public void set(@Nullable Map carrier, String key, String value) { + if (carrier == null || key == null || value == null) { + return; + } + // Spec recommends using uppercase for environment variable names. + carrier.put(key.toUpperCase(Locale.ROOT), value); + } + + @Override + public String toString() { + return "EnvironmentSetter"; + } +} diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java new file mode 100644 index 00000000000..0370beb885b --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.propagation; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class EnvironmentGetterTest { + + @Test + void get() { + Map carrier = new HashMap<>(); + carrier.put("TRACEPARENT", "val1"); + carrier.put("TRACESTATE", "val2"); + carrier.put("BAGGAGE", "val3"); + carrier.put("OTHER", "val4"); + + assertThat(EnvironmentGetter.INSTANCE.get(carrier, "traceparent")).isEqualTo("val1"); + assertThat(EnvironmentGetter.INSTANCE.get(carrier, "TRACESTATE")).isEqualTo("val2"); + assertThat(EnvironmentGetter.INSTANCE.get(carrier, "Baggage")).isEqualTo("val3"); + assertThat(EnvironmentGetter.INSTANCE.get(carrier, "other")).isEqualTo("val4"); + } + + @Test + void get_null() { + assertThat(EnvironmentGetter.INSTANCE.get(null, "key")).isNull(); + assertThat(EnvironmentGetter.INSTANCE.get(Collections.emptyMap(), null)).isNull(); + } + + @Test + void keys() { + Map carrier = new HashMap<>(); + carrier.put("K1", "V1"); + carrier.put("K2", "V2"); + + assertThat(EnvironmentGetter.INSTANCE.keys(carrier)).containsExactlyInAnyOrder("K1", "K2"); + assertThat(EnvironmentGetter.INSTANCE.keys(null)).isEmpty(); + } + + @Test + void testToString() { + assertThat(EnvironmentGetter.INSTANCE.toString()).isEqualTo("EnvironmentGetter"); + } +} diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java new file mode 100644 index 00000000000..02308a7b544 --- /dev/null +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.propagation; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class EnvironmentSetterTest { + + @Test + void set() { + Map carrier = new HashMap<>(); + EnvironmentSetter.INSTANCE.set(carrier, "traceparent", "val1"); + EnvironmentSetter.INSTANCE.set(carrier, "TRACESTATE", "val2"); + EnvironmentSetter.INSTANCE.set(carrier, "Baggage", "val3"); + + assertThat(carrier).containsEntry("TRACEPARENT", "val1"); + assertThat(carrier).containsEntry("TRACESTATE", "val2"); + assertThat(carrier).containsEntry("BAGGAGE", "val3"); + } + + @Test + void set_null() { + Map carrier = new HashMap<>(); + EnvironmentSetter.INSTANCE.set(null, "key", "val"); + EnvironmentSetter.INSTANCE.set(carrier, null, "val"); + EnvironmentSetter.INSTANCE.set(carrier, "key", null); + assertThat(carrier).isEmpty(); + } + + @Test + void testToString() { + assertThat(EnvironmentSetter.INSTANCE.toString()).isEqualTo("EnvironmentSetter"); + } +} From 4cfffacd824f97f00c67487400cde21901a78511 Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Sun, 15 Feb 2026 16:37:30 +0530 Subject: [PATCH 2/6] fix: Align environment carriers with spec requirements --- .../propagation/EnvironmentGetter.java | 30 +++++++++++++++---- .../propagation/EnvironmentSetter.java | 30 +++++++++++++++---- .../propagation/EnvironmentGetterTest.java | 28 +++++++++++------ .../propagation/EnvironmentSetterTest.java | 24 ++++++++++----- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 5dfae10392f..eba1e770c55 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -10,17 +10,32 @@ import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; /** * A {@link TextMapGetter} that extracts context from a map carrier, intended for use with * environment variables. * *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code - * TRACESTATE}, {@code BAGGAGE}). This getter translates keys to uppercase before looking them up in - * the carrier. + * TRACESTATE}, {@code BAGGAGE}). This getter translates keys to uppercase and replaces characters + * not allowed in environment variables (e.g., {@code .} and {@code -}) with underscores before + * looking them up in the carrier. + + * @see Environment + * Variable Format Restrictions */ -public enum EnvironmentGetter implements TextMapGetter> { - INSTANCE; +@Immutable +public final class EnvironmentGetter implements TextMapGetter> { + + private static final EnvironmentGetter INSTANCE = new EnvironmentGetter(); + + private EnvironmentGetter() {} + + /** Returns the singleton instance of {@link EnvironmentGetter}. */ + public static EnvironmentGetter getInstance() { + return INSTANCE; + } @Override public Iterable keys(Map carrier) { @@ -36,8 +51,11 @@ public String get(@Nullable Map carrier, String key) { if (carrier == null || key == null) { return null; } - // Spec recommends using uppercase for environment variable names. - return carrier.get(key.toUpperCase(Locale.ROOT)); + // Spec recommends using uppercase and underscores for environment variable + // names for maximum + // cross-platform compatibility. + String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT); + return carrier.get(sanitizedKey); } @Override diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java index bc94a12af23..656b9638b1d 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -9,25 +9,43 @@ import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; /** * A {@link TextMapSetter} that injects context into a map carrier, intended for use with * environment variables. * *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code - * TRACESTATE}, {@code BAGGAGE}). This setter translates keys to uppercase before inserting them - * into the carrier. + * TRACESTATE}, {@code BAGGAGE}). This setter translates keys to uppercase and replaces characters + * not allowed in environment variables (e.g., {@code .} and {@code -}) with underscores before + * inserting them into the carrier. + * + * @see Environment + * Variable Format Restrictions */ -public enum EnvironmentSetter implements TextMapSetter> { - INSTANCE; +@Immutable +public final class EnvironmentSetter implements TextMapSetter> { + + private static final EnvironmentSetter INSTANCE = new EnvironmentSetter(); + + private EnvironmentSetter() {} + + /** Returns the singleton instance of {@link EnvironmentSetter}. */ + public static EnvironmentSetter getInstance() { + return INSTANCE; + } @Override public void set(@Nullable Map carrier, String key, String value) { if (carrier == null || key == null || value == null) { return; } - // Spec recommends using uppercase for environment variable names. - carrier.put(key.toUpperCase(Locale.ROOT), value); + // Spec recommends using uppercase and underscores for environment variable + // names for maximum + // cross-platform compatibility. + String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT); + carrier.put(sanitizedKey, value); } @Override diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java index 0370beb885b..71807282823 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java @@ -22,16 +22,26 @@ void get() { carrier.put("BAGGAGE", "val3"); carrier.put("OTHER", "val4"); - assertThat(EnvironmentGetter.INSTANCE.get(carrier, "traceparent")).isEqualTo("val1"); - assertThat(EnvironmentGetter.INSTANCE.get(carrier, "TRACESTATE")).isEqualTo("val2"); - assertThat(EnvironmentGetter.INSTANCE.get(carrier, "Baggage")).isEqualTo("val3"); - assertThat(EnvironmentGetter.INSTANCE.get(carrier, "other")).isEqualTo("val4"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "traceparent")).isEqualTo("val1"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "TRACESTATE")).isEqualTo("val2"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "Baggage")).isEqualTo("val3"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "other")).isEqualTo("val4"); + } + + @Test + void get_sanitization() { + Map carrier = new HashMap<>(); + carrier.put("OTEL_TRACE_ID", "val1"); + carrier.put("OTEL_BAGGAGE_KEY", "val2"); + + assertThat(EnvironmentGetter.getInstance().get(carrier, "otel.trace.id")).isEqualTo("val1"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "otel-baggage-key")).isEqualTo("val2"); } @Test void get_null() { - assertThat(EnvironmentGetter.INSTANCE.get(null, "key")).isNull(); - assertThat(EnvironmentGetter.INSTANCE.get(Collections.emptyMap(), null)).isNull(); + assertThat(EnvironmentGetter.getInstance().get(null, "key")).isNull(); + assertThat(EnvironmentGetter.getInstance().get(Collections.emptyMap(), null)).isNull(); } @Test @@ -40,12 +50,12 @@ void keys() { carrier.put("K1", "V1"); carrier.put("K2", "V2"); - assertThat(EnvironmentGetter.INSTANCE.keys(carrier)).containsExactlyInAnyOrder("K1", "K2"); - assertThat(EnvironmentGetter.INSTANCE.keys(null)).isEmpty(); + assertThat(EnvironmentGetter.getInstance().keys(carrier)).containsExactlyInAnyOrder("K1", "K2"); + assertThat(EnvironmentGetter.getInstance().keys(null)).isEmpty(); } @Test void testToString() { - assertThat(EnvironmentGetter.INSTANCE.toString()).isEqualTo("EnvironmentGetter"); + assertThat(EnvironmentGetter.getInstance().toString()).isEqualTo("EnvironmentGetter"); } } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java index 02308a7b544..ea735eeb304 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java @@ -16,26 +16,36 @@ class EnvironmentSetterTest { @Test void set() { Map carrier = new HashMap<>(); - EnvironmentSetter.INSTANCE.set(carrier, "traceparent", "val1"); - EnvironmentSetter.INSTANCE.set(carrier, "TRACESTATE", "val2"); - EnvironmentSetter.INSTANCE.set(carrier, "Baggage", "val3"); + EnvironmentSetter.getInstance().set(carrier, "traceparent", "val1"); + EnvironmentSetter.getInstance().set(carrier, "TRACESTATE", "val2"); + EnvironmentSetter.getInstance().set(carrier, "Baggage", "val3"); assertThat(carrier).containsEntry("TRACEPARENT", "val1"); assertThat(carrier).containsEntry("TRACESTATE", "val2"); assertThat(carrier).containsEntry("BAGGAGE", "val3"); } + @Test + void set_sanitization() { + Map carrier = new HashMap<>(); + EnvironmentSetter.getInstance().set(carrier, "otel.trace.id", "val1"); + EnvironmentSetter.getInstance().set(carrier, "otel-baggage-key", "val2"); + + assertThat(carrier).containsEntry("OTEL_TRACE_ID", "val1"); + assertThat(carrier).containsEntry("OTEL_BAGGAGE_KEY", "val2"); + } + @Test void set_null() { Map carrier = new HashMap<>(); - EnvironmentSetter.INSTANCE.set(null, "key", "val"); - EnvironmentSetter.INSTANCE.set(carrier, null, "val"); - EnvironmentSetter.INSTANCE.set(carrier, "key", null); + EnvironmentSetter.getInstance().set(null, "key", "val"); + EnvironmentSetter.getInstance().set(carrier, null, "val"); + EnvironmentSetter.getInstance().set(carrier, "key", null); assertThat(carrier).isEmpty(); } @Test void testToString() { - assertThat(EnvironmentSetter.INSTANCE.toString()).isEqualTo("EnvironmentSetter"); + assertThat(EnvironmentSetter.getInstance().toString()).isEqualTo("EnvironmentSetter"); } } From b1c2d190ac63c8b2f49ba93f1143f0850756ca1c Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Sun, 15 Feb 2026 17:10:02 +0530 Subject: [PATCH 3/6] chore: Fix spotless formatting violations Apply spotless auto-formatting to align with project code style --- .../api/incubator/propagation/EnvironmentGetter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index eba1e770c55..889a75379a7 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -20,7 +20,7 @@ * TRACESTATE}, {@code BAGGAGE}). This getter translates keys to uppercase and replaces characters * not allowed in environment variables (e.g., {@code .} and {@code -}) with underscores before * looking them up in the carrier. - + * * @see Environment * Variable Format Restrictions From c9cdbe481612e3b3aa6bf72ee318ddc5f70ce333 Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Mon, 16 Feb 2026 21:35:00 +0530 Subject: [PATCH 4/6] fix: refactor environment carriers to standard singleton and fix CI failures --- .../api/incubator/propagation/EnvironmentGetter.java | 2 -- .../api/incubator/propagation/EnvironmentSetter.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 889a75379a7..9eb8904730e 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -10,7 +10,6 @@ import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; /** * A {@link TextMapGetter} that extracts context from a map carrier, intended for use with @@ -25,7 +24,6 @@ * "https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/env-carriers.md#format-restrictions">Environment * Variable Format Restrictions */ -@Immutable public final class EnvironmentGetter implements TextMapGetter> { private static final EnvironmentGetter INSTANCE = new EnvironmentGetter(); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java index 656b9638b1d..838ca05070b 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -9,7 +9,6 @@ import java.util.Locale; import java.util.Map; import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; /** * A {@link TextMapSetter} that injects context into a map carrier, intended for use with @@ -24,7 +23,6 @@ * "https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/env-carriers.md#format-restrictions">Environment * Variable Format Restrictions */ -@Immutable public final class EnvironmentSetter implements TextMapSetter> { private static final EnvironmentSetter INSTANCE = new EnvironmentSetter(); From 521c041ccec8f1edb276b1501b887ce60eef9f15 Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Wed, 18 Feb 2026 17:54:25 +0530 Subject: [PATCH 5/6] [incubator] Add format restriction handling for EnvironmentGetter/Setter --- .../propagation/EnvironmentGetter.java | 56 ++++++++++++++++-- .../propagation/EnvironmentSetter.java | 59 +++++++++++++++++-- .../propagation/EnvironmentGetterTest.java | 24 ++++++++ .../propagation/EnvironmentSetterTest.java | 25 ++++++++ 4 files changed, 153 insertions(+), 11 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 9eb8904730e..40f9281081f 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -9,16 +9,34 @@ import java.util.Collections; import java.util.Locale; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; /** * A {@link TextMapGetter} that extracts context from a map carrier, intended for use with - * environment variables. + * environment variables in child processes. * - *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code - * TRACESTATE}, {@code BAGGAGE}). This getter translates keys to uppercase and replaces characters - * not allowed in environment variables (e.g., {@code .} and {@code -}) with underscores before - * looking them up in the carrier. + *

This is useful when a child process needs to extract propagated context from its environment. + * For example: + * + *

{@code
+ * Map env = System.getenv();
+ * Context context = contextPropagators.getTextMapPropagator()
+ *     .extract(Context.current(), env, EnvironmentGetter.getInstance());
+ * }
+ * + *

This getter automatically sanitizes keys to match environment variable naming conventions: + * + *

+ * + *

Values are validated to contain only characters valid in HTTP header fields per RFC 9110 (visible ASCII + * characters, space, and horizontal tab). Values containing invalid characters are treated as + * absent and {@code null} is returned. * * @see Environment @@ -26,6 +44,7 @@ */ public final class EnvironmentGetter implements TextMapGetter> { + private static final Logger logger = Logger.getLogger(EnvironmentGetter.class.getName()); private static final EnvironmentGetter INSTANCE = new EnvironmentGetter(); private EnvironmentGetter() {} @@ -53,7 +72,32 @@ public String get(@Nullable Map carrier, String key) { // names for maximum // cross-platform compatibility. String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT); - return carrier.get(sanitizedKey); + String value = carrier.get(sanitizedKey); + if (value != null && !isValidHttpHeaderValue(value)) { + logger.log( + Level.FINE, + "Ignoring environment variable ''{0}'': " + + "value contains characters not valid in HTTP header fields per RFC 9110.", + sanitizedKey); + return null; + } + return value; + } + + /** + * Checks whether a string contains only characters valid in HTTP header field values per RFC 9110 Section 5.5. + * Valid characters are: visible ASCII (0x21-0x7E), space (0x20), and horizontal tab (0x09). + */ + private static boolean isValidHttpHeaderValue(String value) { + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + // VCHAR (0x21-0x7E), SP (0x20), HTAB (0x09) + if (ch != '\t' && (ch < ' ' || ch > '~')) { + return false; + } + } + return true; } @Override diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java index 838ca05070b..520da6b0264 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -8,16 +8,40 @@ import io.opentelemetry.context.propagation.TextMapSetter; import java.util.Locale; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; /** * A {@link TextMapSetter} that injects context into a map carrier, intended for use with - * environment variables. + * environment variables when spawning child processes. * - *

Standard environment variable names are uppercase (e.g., {@code TRACEPARENT}, {@code - * TRACESTATE}, {@code BAGGAGE}). This setter translates keys to uppercase and replaces characters - * not allowed in environment variables (e.g., {@code .} and {@code -}) with underscores before - * inserting them into the carrier. + *

This is useful when an application needs to propagate context to sub-processes via their + * environment. For example, when using {@link ProcessBuilder}: + * + *

{@code
+ * Map env = new HashMap<>();
+ * contextPropagators.getTextMapPropagator().inject(context, env, EnvironmentSetter.getInstance());
+ * ProcessBuilder processBuilder = new ProcessBuilder();
+ * processBuilder.environment().putAll(env);
+ * }
+ * + *

This setter automatically sanitizes keys to be compatible with environment variable naming + * conventions: + * + *

+ * + *

Values are validated to contain only characters valid in HTTP header fields per RFC 9110 (visible ASCII + * characters, space, and horizontal tab). Values containing invalid characters are silently + * skipped. + * + *

Size limitations: Environment variable sizes are platform-dependent (e.g., + * Windows limits name=value pairs to 32,767 characters). Callers are responsible for being aware of + * platform-specific limits when injecting context. * * @see Environment @@ -25,6 +49,7 @@ */ public final class EnvironmentSetter implements TextMapSetter> { + private static final Logger logger = Logger.getLogger(EnvironmentSetter.class.getName()); private static final EnvironmentSetter INSTANCE = new EnvironmentSetter(); private EnvironmentSetter() {} @@ -39,6 +64,14 @@ public void set(@Nullable Map carrier, String key, String value) if (carrier == null || key == null || value == null) { return; } + if (!isValidHttpHeaderValue(value)) { + logger.log( + Level.FINE, + "Skipping environment variable injection for key ''{0}'': " + + "value contains characters not valid in HTTP header fields per RFC 9110.", + key); + return; + } // Spec recommends using uppercase and underscores for environment variable // names for maximum // cross-platform compatibility. @@ -46,6 +79,22 @@ public void set(@Nullable Map carrier, String key, String value) carrier.put(sanitizedKey, value); } + /** + * Checks whether a string contains only characters valid in HTTP header field values per RFC 9110 Section 5.5. + * Valid characters are: visible ASCII (0x21-0x7E), space (0x20), and horizontal tab (0x09). + */ + private static boolean isValidHttpHeaderValue(String value) { + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + // VCHAR (0x21-0x7E), SP (0x20), HTAB (0x09) + if (ch != '\t' && (ch < ' ' || ch > '~')) { + return false; + } + } + return true; + } + @Override public String toString() { return "EnvironmentSetter"; diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java index 71807282823..3f1895cdd10 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java @@ -54,6 +54,30 @@ void keys() { assertThat(EnvironmentGetter.getInstance().keys(null)).isEmpty(); } + @Test + void get_validHeaderValues() { + Map carrier = new HashMap<>(); + carrier.put("KEY1", "simple-value"); + carrier.put("KEY2", "value with spaces"); + carrier.put("KEY3", "value\twith\ttabs"); + + assertThat(EnvironmentGetter.getInstance().get(carrier, "key1")).isEqualTo("simple-value"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "key2")).isEqualTo("value with spaces"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "key3")).isEqualTo("value\twith\ttabs"); + } + + @Test + void get_invalidHeaderValues() { + Map carrier = new HashMap<>(); + carrier.put("KEY1", "value\u0000with\u0001control"); + carrier.put("KEY2", "value\nwith\nnewlines"); + carrier.put("KEY3", "value\u0080non-ascii"); + + assertThat(EnvironmentGetter.getInstance().get(carrier, "key1")).isNull(); + assertThat(EnvironmentGetter.getInstance().get(carrier, "key2")).isNull(); + assertThat(EnvironmentGetter.getInstance().get(carrier, "key3")).isNull(); + } + @Test void testToString() { assertThat(EnvironmentGetter.getInstance().toString()).isEqualTo("EnvironmentGetter"); diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java index ea735eeb304..de166401af6 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java @@ -44,6 +44,31 @@ void set_null() { assertThat(carrier).isEmpty(); } + @Test + void set_validHeaderValues() { + Map carrier = new HashMap<>(); + // Printable ASCII and tab are valid per RFC 9110 + EnvironmentSetter.getInstance().set(carrier, "key1", "simple-value"); + EnvironmentSetter.getInstance().set(carrier, "key2", "value with spaces"); + EnvironmentSetter.getInstance().set(carrier, "key3", "value\twith\ttabs"); + + assertThat(carrier).containsEntry("KEY1", "simple-value"); + assertThat(carrier).containsEntry("KEY2", "value with spaces"); + assertThat(carrier).containsEntry("KEY3", "value\twith\ttabs"); + } + + @Test + void set_invalidHeaderValues() { + Map carrier = new HashMap<>(); + // Control characters and non-ASCII are invalid per RFC 9110 + EnvironmentSetter.getInstance().set(carrier, "key1", "value\u0000with\u0001control"); + EnvironmentSetter.getInstance().set(carrier, "key2", "value\nwith\nnewlines"); + EnvironmentSetter.getInstance().set(carrier, "key3", "value\rwith\rcarriage"); + EnvironmentSetter.getInstance().set(carrier, "key4", "value\u0080non-ascii"); + + assertThat(carrier).isEmpty(); + } + @Test void testToString() { assertThat(EnvironmentSetter.getInstance().toString()).isEqualTo("EnvironmentSetter"); From b8d7f67f7251cf6ebd1680de6131397b41c77fa8 Mon Sep 17 00:00:00 2001 From: Bhagirath00 Date: Thu, 19 Feb 2026 08:37:26 +0530 Subject: [PATCH 6/6] Remove duplicate isValidHttpHeaderValue and fix log message quoting --- .../propagation/EnvironmentGetter.java | 20 ++----------------- .../propagation/EnvironmentSetter.java | 2 +- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 40f9281081f..deead027313 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -73,10 +73,10 @@ public String get(@Nullable Map carrier, String key) { // cross-platform compatibility. String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT); String value = carrier.get(sanitizedKey); - if (value != null && !isValidHttpHeaderValue(value)) { + if (value != null && !EnvironmentSetter.isValidHttpHeaderValue(value)) { logger.log( Level.FINE, - "Ignoring environment variable ''{0}'': " + "Ignoring environment variable '{0}': " + "value contains characters not valid in HTTP header fields per RFC 9110.", sanitizedKey); return null; @@ -84,22 +84,6 @@ public String get(@Nullable Map carrier, String key) { return value; } - /** - * Checks whether a string contains only characters valid in HTTP header field values per RFC 9110 Section 5.5. - * Valid characters are: visible ASCII (0x21-0x7E), space (0x20), and horizontal tab (0x09). - */ - private static boolean isValidHttpHeaderValue(String value) { - for (int i = 0; i < value.length(); i++) { - char ch = value.charAt(i); - // VCHAR (0x21-0x7E), SP (0x20), HTAB (0x09) - if (ch != '\t' && (ch < ' ' || ch > '~')) { - return false; - } - } - return true; - } - @Override public String toString() { return "EnvironmentGetter"; diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java index 520da6b0264..a4dd5974751 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -84,7 +84,7 @@ public void set(@Nullable Map carrier, String key, String value) * href="https://datatracker.ietf.org/doc/html/rfc9110#section-5.5">RFC 9110 Section 5.5. * Valid characters are: visible ASCII (0x21-0x7E), space (0x20), and horizontal tab (0x09). */ - private static boolean isValidHttpHeaderValue(String value) { + static boolean isValidHttpHeaderValue(String value) { for (int i = 0; i < value.length(); i++) { char ch = value.charAt(i); // VCHAR (0x21-0x7E), SP (0x20), HTAB (0x09)