diff --git a/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift b/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift index 05c3e7b4..7f3848d4 100644 --- a/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift +++ b/Sources/OpenTelemetrySdk/Trace/Propagation/EnvironmentContextPropagator.swift @@ -67,14 +67,19 @@ public struct EnvironmentMappingSetter: Setter { /** * EnvironmentMappingGetter adapts a `Getter` to normalize propagation keys to environment variable format. * - * Normalization rules per OpenTelemetry specification: - * - Converts keys to uppercase (e.g., "traceparent" → "TRACEPARENT") - * - Replaces all non-alphanumeric characters (except underscore) with underscores - * (e.g., "x-b3-traceid" → "X_B3_TRACEID") + * Per the OpenTelemetry specification, it normalizes **only the requested key** and performs a + * direct lookup in the carrier using that normalized name. Non-normalized carrier keys + * (e.g. `"traceparent"` instead of `"TRACEPARENT"`) are silently ignored. + * + * Normalization rules: + * - Converts the requested key to uppercase (e.g., `"traceparent"` → `"TRACEPARENT"`) + * - Replaces every character that is not an ASCII letter, digit, or underscore with `_` + * (e.g., `"x-b3-traceid"` → `"X_B3_TRACEID"`) + * - Prefixes the result with `_` if it would otherwise start with an ASCII digit * * Usage: * ```swift - * let innerGetter = EnvironmentVariableGetter() + * let innerGetter = CapturingGetter() * let envGetter = EnvironmentMappingGetter(innerGetter: innerGetter) * let spanContext = w3cPropagator.extract(carrier: ProcessInfo.processInfo.environment, getter: envGetter) * ``` @@ -90,14 +95,7 @@ public struct EnvironmentMappingGetter: Getter { public func get(carrier: [String: String], key: String) -> [String]? { let mappedKey = normalizeKeyForEnvironment(key) - if carrier[mappedKey] != nil { - return innerGetter.get(carrier: carrier, key: mappedKey) - } - let normalizedCarrier = Dictionary( - carrier.map { (normalizeKeyForEnvironment($0.key), $0.value) }, - uniquingKeysWith: { first, _ in first } - ) - return innerGetter.get(carrier: normalizedCarrier, key: mappedKey) + return innerGetter.get(carrier: carrier, key: mappedKey) } } diff --git a/Tests/OpenTelemetrySdkTests/Trace/EnvironmentContextPropagatorTests.swift b/Tests/OpenTelemetrySdkTests/Trace/EnvironmentContextPropagatorTests.swift index 39074d2e..756dfcbd 100644 --- a/Tests/OpenTelemetrySdkTests/Trace/EnvironmentContextPropagatorTests.swift +++ b/Tests/OpenTelemetrySdkTests/Trace/EnvironmentContextPropagatorTests.swift @@ -123,10 +123,11 @@ class EnvironmentContextPropagatorTests: XCTestCase { XCTAssertEqual(getter.get(carrier: carrier, key: "traceparent"), ["value"]) } - func testGetterNormalizesCarrierKeysWhenDirectLookupFails() { - // Carrier stored with lowercase; getter should normalize both sides and still find the value. + func testGetterIgnoresNonNormalizedCarrierKeys() { + // Carrier stored with a non-normalized key; getter must NOT fall back to normalizing + // carrier keys, only the requested key is normalized. let carrier = ["traceparent": "value"] - XCTAssertEqual(getter.get(carrier: carrier, key: "traceparent"), ["value"]) + XCTAssertNil(getter.get(carrier: carrier, key: "traceparent")) } func testGetterReturnsNilForMissingKey() {