From 9d975b9586d58d3579bdd42ffb8f7ca268a8a83d Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 24 Oct 2025 08:17:41 -0400 Subject: [PATCH 1/2] add bean override and cache for spring environment calls --- .../OpenTelemetryAutoConfiguration.java | 1 + .../properties/SpringConfigProperties.java | 201 ++++++++++++------ .../OpenTelemetryAutoConfigurationTest.java | 22 ++ .../SpringConfigPropertiesTest.java | 70 +++++- 4 files changed, 232 insertions(+), 62 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index b9a9bd7c9a2a..1d894e0330dc 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -100,6 +100,7 @@ public ResourceProvider otelDistroVersionResourceProvider() { } @Bean + @ConditionalOnMissingBean public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( Environment env, OtlpExporterProperties otlpExporterProperties, diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 4f0412ef023b..3111568fe34d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -15,6 +15,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.springframework.core.env.Environment; import org.springframework.expression.ExpressionParser; @@ -34,6 +36,23 @@ public class SpringConfigProperties implements ConfigProperties { private final ConfigProperties customizedListProperties; private final Map> listPropertyValues; + private final ConcurrentHashMap> cachedStringValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedBooleanValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedIntValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedLongValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedDoubleValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> cachedListValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedDurationValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> cachedMapValues = + new ConcurrentHashMap<>(); + static final String DISABLED_KEY = "otel.java.disabled.resource.providers"; static final String ENABLED_KEY = "otel.java.enabled.resource.providers"; @@ -151,106 +170,166 @@ public static ConfigProperties create( @Nullable @Override public String getString(String name) { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - String value = environment.getProperty(normalizedName, String.class); - if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { - // SDK autoconfigure module defaults to `grpc`, but this module aligns with recommendation - // in specification to default to `http/protobuf` - return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; - } - return or(value, otelSdkProperties.getString(name)); + return cachedStringValues + .computeIfAbsent( + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + String value = environment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with + // recommendation in specification to default to `http/protobuf` + return Optional.of(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF); + } + return Optional.ofNullable(or(value, otelSdkProperties.getString(key))); + }) + .orElse(null); } @Nullable @Override public Boolean getBoolean(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), - otelSdkProperties.getBoolean(name)); + return cachedBooleanValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), + otelSdkProperties.getBoolean(name)))) + .orElse(null); } @Nullable @Override public Integer getInt(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), - otelSdkProperties.getInt(name)); + return cachedIntValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), + otelSdkProperties.getInt(key)))) + .orElse(null); } @Nullable @Override public Long getLong(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), - otelSdkProperties.getLong(name)); + return cachedLongValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), + otelSdkProperties.getLong(key)))) + .orElse(null); } @Nullable @Override public Double getDouble(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), - otelSdkProperties.getDouble(name)); + return cachedDoubleValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), + otelSdkProperties.getDouble(key)))) + .orElse(null); } @SuppressWarnings("unchecked") @Override public List getList(String name) { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + return cachedListValues + .computeIfAbsent( + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - List list = listPropertyValues.get(normalizedName); - if (list != null) { - List c = customizedListProperties.getList(name); - if (!c.isEmpty()) { - return c; - } - if (!list.isEmpty()) { - return list; - } - } + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(key); + if (!c.isEmpty()) { + return Optional.of(c); + } + if (!list.isEmpty()) { + return Optional.of(list); + } + } - return or(environment.getProperty(normalizedName, List.class), otelSdkProperties.getList(name)); + List envValue = + (List) environment.getProperty(normalizedName, List.class); + return Optional.ofNullable(or(envValue, otelSdkProperties.getList(key))); + }) + .orElse(null); } @Nullable @Override public Duration getDuration(String name) { - String value = getString(name); - if (value == null) { - return otelSdkProperties.getDuration(name); - } - return DefaultConfigProperties.createFromMap(Collections.singletonMap(name, value)) - .getDuration(name); + return cachedDurationValues + .computeIfAbsent( + name, + key -> { + String value = getString(key); + if (value == null) { + return Optional.ofNullable(otelSdkProperties.getDuration(key)); + } + return Optional.ofNullable( + DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) + .getDuration(key)); + }) + .orElse(null); } @SuppressWarnings("unchecked") @Override public Map getMap(String name) { - Map otelSdkMap = otelSdkProperties.getMap(name); - - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - // maps from config properties are not supported by Environment, so we have to fake it - switch (normalizedName) { - case "otel.resource.attributes": - return mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap); - case "otel.exporter.otlp.headers": - return mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap); - case "otel.exporter.otlp.logs.headers": - return mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap); - case "otel.exporter.otlp.metrics.headers": - return mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap); - case "otel.exporter.otlp.traces.headers": - return mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap); - default: - break; - } + return cachedMapValues + .computeIfAbsent( + name, + key -> { + Map otelSdkMap = otelSdkProperties.getMap(name); - String value = environment.getProperty(normalizedName); - if (value == null) { - return otelSdkMap; - } - return (Map) parser.parseExpression(value).getValue(); + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + // maps from config properties are not supported by Environment, so we have to fake it + switch (normalizedName) { + case "otel.resource.attributes": + return Optional.of(mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap)); + case "otel.exporter.otlp.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.logs.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.metrics.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.traces.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap)); + default: + break; + } + + String value = environment.getProperty(normalizedName); + if (value == null) { + return Optional.of(otelSdkMap); + } + return Optional.ofNullable( + (Map) parser.parseExpression(value).getValue()); + }) + .orElse(null); } /** diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 1e1a04bb5f10..af5ce6e12dc8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -58,6 +59,27 @@ void customOpenTelemetry() { .hasBean("otelProperties")); } + private static class CustomAutoConfiguredSdkConfiguration { + @Bean + public AutoConfiguredOpenTelemetrySdk customAutoConfiguredSdk() { + return AutoConfiguredOpenTelemetrySdk.builder().build(); + } + } + + @Test + @DisplayName( + "when Application Context contains AutoConfiguredOpenTelemetrySdk bean should NOT use default") + void customAutoConfiguredOpenTelemetrySdk() { + this.contextRunner + .withUserConfiguration(CustomAutoConfiguredSdkConfiguration.class) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> + assertThat(context) + .hasBean("customAutoConfiguredSdk") + .doesNotHaveBean("autoConfiguredOpenTelemetrySdk")); + } + @Test @DisplayName( "when Application Context DOES NOT contain OpenTelemetry bean should initialize openTelemetry") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java index 02433342b1ab..61ea7ed3905a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java @@ -5,8 +5,13 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; @@ -17,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -162,8 +168,70 @@ void shouldInitializeAttributesByMap() { }); } + public static Stream propertyCachingTestCases() { + return Stream.of( + // property, typeClass, assertion + Arguments.of( + "otel.service.name=test-service", + String.class, + (Consumer) + config -> + assertThat(config.getString("otel.service.name")).isEqualTo("test-service")), + Arguments.of( + "otel.exporter.otlp.enabled=true", + Boolean.class, + (Consumer) + config -> assertThat(config.getBoolean("otel.exporter.otlp.enabled")).isTrue()), + Arguments.of( + "otel.metric.export.interval=10", + Integer.class, + (Consumer) + config -> assertThat(config.getInt("otel.metric.export.interval")).isEqualTo(10)), + Arguments.of( + "otel.bsp.schedule.delay=5000", + Long.class, + (Consumer) + config -> assertThat(config.getLong("otel.bsp.schedule.delay")).isEqualTo(5000L)), + Arguments.of( + "otel.traces.sampler.arg=0.5", + Double.class, + (Consumer) + config -> assertThat(config.getDouble("otel.traces.sampler.arg")).isEqualTo(0.5))); + } + + @ParameterizedTest + @MethodSource("propertyCachingTestCases") + @DisplayName("should cache property lookups and call Environment.getProperty() only once") + void propertyCaching( + String property, Class typeClass, Consumer assertion) { + String propertyName = property.split("=")[0]; + + this.contextRunner + .withPropertyValues(property) + .run( + context -> { + Environment realEnvironment = context.getBean("environment", Environment.class); + Environment spyEnvironment = spy(realEnvironment); + + SpringConfigProperties config = + new SpringConfigProperties( + spyEnvironment, + new SpelExpressionParser(), + context.getBean(OtlpExporterProperties.class), + context.getBean(OtelResourceProperties.class), + context.getBean(OtelSpringProperties.class), + DefaultConfigProperties.createFromMap(emptyMap())); + + for (int i = 0; i < 100; i++) { + assertion.accept(config); + } + + verify(spyEnvironment, times(1)).getProperty(eq(propertyName), eq(typeClass)); + }); + } + private static ConfigProperties getConfig(AssertableApplicationContext context) { - return getConfig(context, Collections.emptyMap()); + return getConfig(context, emptyMap()); } private static SpringConfigProperties getConfig( From 39757e14246bbd005ac4cd9361a78dd28ae791cd Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 24 Oct 2025 09:57:34 -0400 Subject: [PATCH 2/2] use helper methods --- .../properties/SpringConfigProperties.java | 251 +++++++++--------- 1 file changed, 126 insertions(+), 125 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 3111568fe34d..71400bce5daa 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import javax.annotation.Nullable; import org.springframework.core.env.Environment; import org.springframework.expression.ExpressionParser; @@ -167,169 +168,169 @@ public static ConfigProperties create( fallback); } + @Nullable + private static T getCachedValue( + ConcurrentHashMap> cache, + String name, + Function valueFunction) { + return cache + .computeIfAbsent(name, key -> Optional.ofNullable(valueFunction.apply(key))) + .orElse(null); + } + @Nullable @Override public String getString(String name) { - return cachedStringValues - .computeIfAbsent( - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - String value = environment.getProperty(normalizedName, String.class); - if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { - // SDK autoconfigure module defaults to `grpc`, but this module aligns with - // recommendation in specification to default to `http/protobuf` - return Optional.of(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF); - } - return Optional.ofNullable(or(value, otelSdkProperties.getString(key))); - }) - .orElse(null); + return getCachedValue( + cachedStringValues, + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + String value = environment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with + // recommendation in specification to default to `http/protobuf` + return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + } + return or(value, otelSdkProperties.getString(key)); + }); } @Nullable @Override public Boolean getBoolean(String name) { - return cachedBooleanValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), - otelSdkProperties.getBoolean(name)))) - .orElse(null); + return getCachedValue( + cachedBooleanValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Boolean.class), + otelSdkProperties.getBoolean(key))); } @Nullable @Override public Integer getInt(String name) { - return cachedIntValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), - otelSdkProperties.getInt(key)))) - .orElse(null); + return getCachedValue( + cachedIntValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), + otelSdkProperties.getInt(key))); } @Nullable @Override public Long getLong(String name) { - return cachedLongValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), - otelSdkProperties.getLong(key)))) - .orElse(null); + return getCachedValue( + cachedLongValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), + otelSdkProperties.getLong(key))); } @Nullable @Override public Double getDouble(String name) { - return cachedDoubleValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), - otelSdkProperties.getDouble(key)))) - .orElse(null); + return getCachedValue( + cachedDoubleValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), + otelSdkProperties.getDouble(key))); } @SuppressWarnings("unchecked") @Override public List getList(String name) { - - return cachedListValues - .computeIfAbsent( - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - - List list = listPropertyValues.get(normalizedName); - if (list != null) { - List c = customizedListProperties.getList(key); - if (!c.isEmpty()) { - return Optional.of(c); - } - if (!list.isEmpty()) { - return Optional.of(list); - } - } - - List envValue = - (List) environment.getProperty(normalizedName, List.class); - return Optional.ofNullable(or(envValue, otelSdkProperties.getList(key))); - }) - .orElse(null); + return getCachedValue( + cachedListValues, + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(key); + if (!c.isEmpty()) { + return c; + } + if (!list.isEmpty()) { + return list; + } + } + + List envValue = + (List) environment.getProperty(normalizedName, List.class); + return or(envValue, otelSdkProperties.getList(key)); + }); } @Nullable @Override public Duration getDuration(String name) { - return cachedDurationValues - .computeIfAbsent( - name, - key -> { - String value = getString(key); - if (value == null) { - return Optional.ofNullable(otelSdkProperties.getDuration(key)); - } - return Optional.ofNullable( - DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) - .getDuration(key)); - }) - .orElse(null); + return getCachedValue( + cachedDurationValues, + name, + key -> { + String value = getString(key); + if (value == null) { + return otelSdkProperties.getDuration(key); + } + return DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) + .getDuration(key); + }); } @SuppressWarnings("unchecked") @Override public Map getMap(String name) { - return cachedMapValues - .computeIfAbsent( - name, - key -> { - Map otelSdkMap = otelSdkProperties.getMap(name); - - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - // maps from config properties are not supported by Environment, so we have to fake it - switch (normalizedName) { - case "otel.resource.attributes": - return Optional.of(mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap)); - case "otel.exporter.otlp.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.logs.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.metrics.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.traces.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap)); - default: - break; - } - - String value = environment.getProperty(normalizedName); - if (value == null) { - return Optional.of(otelSdkMap); - } - return Optional.ofNullable( - (Map) parser.parseExpression(value).getValue()); - }) - .orElse(null); + return getCachedValue( + cachedMapValues, + name, + key -> { + Map otelSdkMap = otelSdkProperties.getMap(key); + + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + // maps from config properties are not supported by Environment, so we have to fake it + Map specialMap = getSpecialMapProperty(normalizedName, otelSdkMap); + if (specialMap != null) { + return specialMap; + } + + String value = environment.getProperty(normalizedName); + if (value == null) { + return otelSdkMap; + } + return (Map) parser.parseExpression(value).getValue(); + }); + } + + @Nullable + private Map getSpecialMapProperty( + String normalizedName, Map otelSdkMap) { + switch (normalizedName) { + case "otel.resource.attributes": + return mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap); + case "otel.exporter.otlp.headers": + return mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap); + case "otel.exporter.otlp.logs.headers": + return mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.metrics.headers": + return mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.traces.headers": + return mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap); + default: + return null; + } } /**