diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index f4dbe222bfe2..a1f22d1b0bbe 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -348,6 +348,13 @@ tasks { isEnabled = testSpring3 // same condition as Spring 3 (requires Java 17+) } + withType().configureEach { + // for @SetEnvironmentVariable + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + } + named("jar") { from(sourceSets["javaSpring3"].output) from(sourceSets["javaSpring4"].output) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java index 46409dc52149..b2222baf489f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java @@ -46,25 +46,20 @@ class EmbeddedConfigFile { private EmbeddedConfigFile() {} static OpenTelemetryConfigurationModel extractModel(ConfigurableEnvironment environment) { - Map props = extractSpringProperties(environment); - Map nested = convertFlatPropsToNested(props); - return MAPPER.convertValue(nested, OpenTelemetryConfigurationModel.class); + Map props = extractSpringProperties(environment); + return convertToOpenTelemetryConfigurationModel(props); } - private static Map extractSpringProperties(ConfigurableEnvironment environment) { + private static Map extractSpringProperties(ConfigurableEnvironment environment) { MutablePropertySources propertySources = environment.getPropertySources(); - Map props = new HashMap<>(); + Map props = new HashMap<>(); for (PropertySource propertySource : propertySources) { if (propertySource instanceof EnumerablePropertySource) { for (String propertyName : ((EnumerablePropertySource) propertySource).getPropertyNames()) { if (propertyName.startsWith("otel.")) { - Object property = propertySource.getProperty(propertyName); - // Resolve ${} placeholders in String values while preserving types for others - if (property instanceof String) { - property = environment.resolvePlaceholders((String) property); - } + String property = environment.getProperty(propertyName); if (Objects.equals(property, "")) { property = null; // spring returns empty string for yaml null } @@ -100,18 +95,29 @@ private static Map extractSpringProperties(ConfigurableEnvironme return props; } + static OpenTelemetryConfigurationModel convertToOpenTelemetryConfigurationModel( + Map flatProps) { + Map nested = convertFlatPropsToNested(flatProps); + + return getObjectMapper().convertValue(nested, OpenTelemetryConfigurationModel.class); + } + + static ObjectMapper getObjectMapper() { + return MAPPER; + } + /** * Convert flat property map to nested structure. e.g. "otel.instrumentation.java.list[0]" = "one" * and "otel.instrumentation.java.list[1]" = "two" becomes: {otel: {instrumentation: {java: {list: * ["one", "two"]}}}} */ @SuppressWarnings("unchecked") - static Map convertFlatPropsToNested(Map flatProps) { + static Map convertFlatPropsToNested(Map flatProps) { Map result = new HashMap<>(); - for (Map.Entry entry : flatProps.entrySet()) { + for (Map.Entry entry : flatProps.entrySet()) { String key = entry.getKey(); - Object value = entry.getValue(); + String value = entry.getValue(); // Split the key by dots String[] parts = key.split("\\."); 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 72466b46d03e..87883de555d8 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 @@ -169,7 +169,7 @@ public OpenTelemetry openTelemetry( model, new OpenTelemetrySdkComponentLoader(applicationContext)); Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); logStart(); - return sdk; + return new SpringOpenTelemetrySdk(sdk, SpringConfigProvider.create(model)); } /** diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringConfigProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringConfigProvider.java new file mode 100644 index 000000000000..a3e06119461e --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringConfigProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.util.Collections; +import java.util.Map; + +/** + * Spring flavor of {@link io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider} + * that tries to coerce types, because spring doesn't tell what the original type was. + * + *

The entire class is a copy of SdkConfigProvider + * which uses {@link SpringDeclarativeConfigProperties} instead of {@link + * io.opentelemetry.sdk.extension.incubator.fileconfig.YamlDeclarativeConfigProperties}. + */ +final class SpringConfigProvider implements ConfigProvider { + + private final DeclarativeConfigProperties instrumentationConfig; + + private SpringConfigProvider( + OpenTelemetryConfigurationModel model, ComponentLoader componentLoader) { + DeclarativeConfigProperties configProperties = toConfigProperties(model, componentLoader); + this.instrumentationConfig = configProperties.get("instrumentation/development"); + } + + private static DeclarativeConfigProperties toConfigProperties( + Object model, ComponentLoader componentLoader) { + Map configurationMap = + EmbeddedConfigFile.getObjectMapper() + .convertValue(model, new TypeReference>() {}); + if (configurationMap == null) { + configurationMap = Collections.emptyMap(); + } + return SpringDeclarativeConfigProperties.create(configurationMap, componentLoader); + } + + /** + * Create a {@link SpringConfigProvider} from the {@code model}. + * + * @param model the configuration model + * @return the {@link SpringConfigProvider} + */ + static SpringConfigProvider create(OpenTelemetryConfigurationModel model) { + return new SpringConfigProvider( + model, ComponentLoader.forClassLoader(SpringConfigProvider.class.getClassLoader())); + } + + @Override + public DeclarativeConfigProperties getInstrumentationConfig() { + return instrumentationConfig; + } + + @Override + public String toString() { + return "SpringConfigProvider{instrumentationConfig=" + instrumentationConfig + '}'; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java new file mode 100644 index 000000000000..26b2e462c99f --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java @@ -0,0 +1,319 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import javax.annotation.Nullable; + +/** + * Spring flavor of {@link + * io.opentelemetry.sdk.extension.incubator.fileconfig.YamlDeclarativeConfigProperties}, that tries + * to coerce types, because spring doesn't tell what the original type was. + * + *

The entire class is a copy of YamlDeclarativeConfigProperties + * with only minor modifications to type coercion logic. + */ +final class SpringDeclarativeConfigProperties implements DeclarativeConfigProperties { + + private static final Set> SUPPORTED_SCALAR_TYPES = + Collections.unmodifiableSet( + new LinkedHashSet<>( + Arrays.asList(String.class, Boolean.class, Long.class, Double.class))); + + /** Values are {@link #isPrimitive(Object)}, {@link List} of scalars. */ + private final Map simpleEntries; + + private final Map> listEntries; + private final Map mapEntries; + private final ComponentLoader componentLoader; + + private SpringDeclarativeConfigProperties( + Map simpleEntries, + Map> listEntries, + Map mapEntries, + ComponentLoader componentLoader) { + this.simpleEntries = simpleEntries; + this.listEntries = listEntries; + this.mapEntries = mapEntries; + this.componentLoader = componentLoader; + } + + /** + * Create a {@link SpringDeclarativeConfigProperties} from the {@code properties} map. + * + *

{@code properties} is expected to be the output of YAML parsing (i.e. with Jackson {@code + * com.fasterxml.jackson.databind.ObjectMapper}), and have values which are scalars, lists of + * scalars, lists of maps, and maps. + * + * @see DeclarativeConfiguration#toConfigProperties(Object) + */ + @SuppressWarnings("unchecked") + public static SpringDeclarativeConfigProperties create( + Map properties, ComponentLoader componentLoader) { + Map simpleEntries = new LinkedHashMap<>(); + Map> listEntries = new LinkedHashMap<>(); + Map mapEntries = new LinkedHashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (isPrimitive(value) || value == null) { + simpleEntries.put(key, value); + continue; + } + if (isPrimitiveList(value)) { + simpleEntries.put(key, value); + continue; + } + if (isListOfMaps(value)) { + List list = + ((List>) value) + .stream() + .map(map -> SpringDeclarativeConfigProperties.create(map, componentLoader)) + .collect(toList()); + listEntries.put(key, list); + continue; + } + if (isMap(value)) { + SpringDeclarativeConfigProperties configProperties = + SpringDeclarativeConfigProperties.create((Map) value, componentLoader); + mapEntries.put(key, configProperties); + continue; + } + throw new DeclarativeConfigException( + "Unable to initialize ExtendedConfigProperties. Key \"" + + key + + "\" has unrecognized object type " + + value.getClass().getName()); + } + return new SpringDeclarativeConfigProperties( + simpleEntries, listEntries, mapEntries, componentLoader); + } + + private static boolean isPrimitiveList(Object object) { + if (object instanceof List) { + List list = (List) object; + return list.stream().allMatch(SpringDeclarativeConfigProperties::isPrimitive); + } + return false; + } + + private static boolean isPrimitive(Object object) { + return object instanceof String + || object instanceof Integer + || object instanceof Long + || object instanceof Float + || object instanceof Double + || object instanceof Boolean; + } + + private static boolean isListOfMaps(Object object) { + if (object instanceof List) { + List list = (List) object; + return list.stream() + .allMatch( + entry -> + entry instanceof Map + && ((Map) entry) + .keySet().stream().allMatch(key -> key instanceof String)); + } + return false; + } + + private static boolean isMap(Object object) { + if (object instanceof Map) { + Map map = (Map) object; + return map.keySet().stream().allMatch(entry -> entry instanceof String); + } + return false; + } + + @Nullable + @Override + public String getString(String name) { + return stringOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Boolean getBoolean(String name) { + return booleanOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Integer getInt(String name) { + Object value = simpleEntries.get(name); + if (value == null) { + return null; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Long) { + return ((Long) value).intValue(); + } + return Integer.parseInt(value.toString()); + } + + @Nullable + @Override + public Long getLong(String name) { + return longOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Double getDouble(String name) { + return doubleOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public List getScalarList(String name, Class scalarType) { + if (!SUPPORTED_SCALAR_TYPES.contains(scalarType)) { + throw new DeclarativeConfigException( + "Unsupported scalar type " + + scalarType.getName() + + ". Supported types include " + + SUPPORTED_SCALAR_TYPES.stream() + .map(Class::getName) + .collect(joining(",", "[", "]"))); + } + Object value = simpleEntries.get(name); + if (value instanceof List) { + List objectList = ((List) value); + if (objectList.isEmpty()) { + return Collections.emptyList(); + } + List result = + objectList.stream() + .map( + entry -> { + if (scalarType == String.class) { + return stringOrNull(entry); + } else if (scalarType == Boolean.class) { + return booleanOrNull(entry); + } else if (scalarType == Long.class) { + return longOrNull(entry); + } else if (scalarType == Double.class) { + return doubleOrNull(entry); + } + return null; + }) + .filter(Objects::nonNull) + .map(scalarType::cast) + .collect(toList()); + if (result.isEmpty()) { + return null; + } + return result; + } + return null; + } + + @Nullable + private static String stringOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + return value.toString(); + } + + @Nullable + private static Boolean booleanOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + return Boolean.parseBoolean(value.toString()); + } + + @Nullable + private static Long longOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Integer) { + return ((Integer) value).longValue(); + } + if (value instanceof Long) { + return (Long) value; + } + return Long.parseLong(value.toString()); + } + + @Nullable + private static Double doubleOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Float) { + return ((Float) value).doubleValue(); + } + if (value instanceof Double) { + return (Double) value; + } + return Double.parseDouble(value.toString()); + } + + @Nullable + @Override + public DeclarativeConfigProperties getStructured(String name) { + return mapEntries.get(name); + } + + @Nullable + @Override + public List getStructuredList(String name) { + List value = listEntries.get(name); + if (value != null) { + return Collections.unmodifiableList(value); + } + return null; + } + + @Override + public Set getPropertyKeys() { + Set keys = new LinkedHashSet<>(); + keys.addAll(simpleEntries.keySet()); + keys.addAll(listEntries.keySet()); + keys.addAll(mapEntries.keySet()); + return Collections.unmodifiableSet(keys); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", ", "SpringDeclarativeConfigProperties{", "}"); + simpleEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + listEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + mapEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + return joiner.toString(); + } + + /** Return the {@link ComponentLoader}. */ + @Override + public ComponentLoader getComponentLoader() { + return componentLoader; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java index 201fda267d22..2396a1147ad3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java @@ -16,7 +16,7 @@ class EmbeddedConfigFileTest { @Test void convertFlatPropsToNested_simpleProperties() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("resource.service.name", "my-service"); flatProps.put("traces.exporter", "otlp"); @@ -55,7 +55,7 @@ void convertFlatPropsToNested_simpleProperties() { @Test void convertFlatPropsToNested_arrayProperties() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("instrumentation.java.list[0]", "one"); flatProps.put("instrumentation.java.list[1]", "two"); flatProps.put("instrumentation.java.list[2]", "three"); @@ -89,7 +89,7 @@ void convertFlatPropsToNested_arrayProperties() { @Test void convertFlatPropsToNested_mixedPropertiesAndArrays() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("resource.service.name", "test-service"); flatProps.put("resource.attributes[0]", "key1=value1"); flatProps.put("resource.attributes[1]", "key2=value2"); @@ -124,7 +124,7 @@ void convertFlatPropsToNested_mixedPropertiesAndArrays() { @Test void convertFlatPropsToNested_emptyMap() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); @@ -133,7 +133,7 @@ void convertFlatPropsToNested_emptyMap() { @Test void convertFlatPropsToNested_singleLevelProperty() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("enabled", "true"); Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); @@ -143,7 +143,7 @@ void convertFlatPropsToNested_singleLevelProperty() { @Test void convertFlatPropsToNested_arrayWithGaps() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("list[0]", "first"); flatProps.put("list[2]", "third"); @@ -161,7 +161,7 @@ void convertFlatPropsToNested_arrayWithGaps() { @Test void convertFlatPropsToNested_deeplyNestedProperties() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("a.b.c.d.e", "deep-value"); Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); @@ -183,7 +183,7 @@ void convertFlatPropsToNested_deeplyNestedProperties() { @Test void convertFlatPropsToNested_nestedArrays() { - Map flatProps = new HashMap<>(); + Map flatProps = new HashMap<>(); flatProps.put("outer[0].inner[0]", "value1"); flatProps.put("outer[0].inner[1]", "value2"); flatProps.put("outer[1].inner[0]", "value3"); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java index da76066daa51..65ba785d14e0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java @@ -8,11 +8,13 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration; import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; import org.springframework.boot.test.context.TestConfiguration; @@ -57,13 +59,92 @@ void initializeProvidersAndOpenTelemetry() { this.contextRunner.run( context -> assertThat(context) - .hasBean("openTelemetry") - .getBean("otelProperties", ConfigProperties.class) + .getBean("openTelemetry", OpenTelemetry.class) .isNotNull() .satisfies( - configProperties -> - assertThat(configProperties.getString("otel.instrumentation.foo.bar")) - .isEqualTo("baz"))); + o -> { + DeclarativeConfigProperties config = + DeclarativeConfigUtil.getInstrumentationConfig(o, "foo"); + assertThat(config.getString("string_key")).isEqualTo("string_value"); + assertThat(config.getBoolean("bool_key")).isTrue(); + assertThat(config.getDouble("double_key")).isEqualTo(3.14); + assertThat(config.getLong("int_key")).isEqualTo(42); + })); + } + + @Test + @SetEnvironmentVariable( + key = "OTEL_INSTRUMENTATION/DEVELOPMENT_JAVA_FOO_STRING_KEY", + value = "new_value") + @SetEnvironmentVariable( + key = "OTEL_INSTRUMENTATION/DEVELOPMENT_JAVA_FOO_BOOL_KEY", + value = "false") + @SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION/DEVELOPMENT_JAVA_FOO_INT_KEY", value = "43") + @SetEnvironmentVariable( + key = "OTEL_INSTRUMENTATION/DEVELOPMENT_JAVA_FOO_DOUBLE_KEY", + value = "4.14") + @SetEnvironmentVariable( + key = "OTEL_TRACER_PROVIDER_PROCESSORS_0_BATCH_EXPORTER_OTLP_HTTP_ENDPOINT", + value = "http://custom:4318/v1/traces") + void envVarOverrideSpringStyle() { + this.contextRunner.run( + context -> + assertThat(context) + .getBean(OpenTelemetry.class) + .isNotNull() + .satisfies( + o -> { + DeclarativeConfigProperties config = + DeclarativeConfigUtil.getInstrumentationConfig(o, "foo"); + assertThat(config.getString("string_key")).isEqualTo("new_value"); + assertThat(config.getBoolean("bool_key")).isFalse(); + assertThat(config.getDouble("double_key")).isEqualTo(4.14); + assertThat(config.getLong("int_key")).isEqualTo(43); + + assertThat(o.toString()) + .contains("OtlpHttpSpanExporter{endpoint=http://custom:4318/v1/traces"); + })); + } + + @Test + @SetEnvironmentVariable(key = "STRING_ENV", value = "string_value") + @SetEnvironmentVariable(key = "BOOL_ENV", value = "true") + @SetEnvironmentVariable(key = "INT_ENV", value = "42") + @SetEnvironmentVariable(key = "DOUBLE_ENV", value = "3.14") + @SetEnvironmentVariable(key = "OTEL_EXPORTER_OTLP_ENDPOINT", value = "http://custom:4318") + void envVarOverrideOtelStyle() { + this.contextRunner.run( + context -> + assertThat(context) + .getBean(OpenTelemetry.class) + .isNotNull() + .satisfies( + o -> { + DeclarativeConfigProperties config = + DeclarativeConfigUtil.getInstrumentationConfig(o, "foo"); + assertThat(config.getString("string_key")).isEqualTo("string_value"); + assertThat(config.getString("string_key_with_env")).isEqualTo("string_value"); + assertThat(config.getString("string_key_with_env_quoted")) + .isEqualTo("string_value"); + + assertThat(config.getBoolean("bool_key")).isTrue(); + assertThat(config.getBoolean("bool_key_with_env")).isTrue(); + assertThat(config.getBoolean("bool_key_with_env_quoted")) + .isTrue(); // quoted "true" works because of coercion + assertThat(config.getString("bool_key_with_env_quoted")).isEqualTo("true"); + + assertThat(config.getDouble("double_key")).isEqualTo(3.14); + assertThat(config.getDouble("double_key_with_env")).isEqualTo(3.14); + assertThat(config.getDouble("double_key_with_env_quoted")) + .isEqualTo(3.14); // quoted "3.14" works because of coercion + assertThat(config.getString("double_key_with_env_quoted")).isEqualTo("3.14"); + + assertThat(config.getLong("int_key")).isEqualTo(42); + assertThat(config.getLong("int_key_with_env")).isEqualTo(42); + assertThat(config.getLong("int_key_with_env_quoted")) + .isEqualTo(42); // quoted "42" works because of coercion + assertThat(config.getString("int_key_with_env_quoted")).isEqualTo("42"); + })); } @Test @@ -126,39 +207,4 @@ void shouldNotLoadInstrumentationWhenExplicitlyDisabled() { "otel.instrumentation/development.java.spring_web.enabled=false") .run(context -> assertThat(context).doesNotHaveBean("otelRestTemplateBeanPostProcessor")); } - - @Test - void envVarOverrideSpringStyle() { - this.contextRunner - // this is typically set via env var - .withSystemProperties( - "OTEL_TRACER_PROVIDER_PROCESSORS_0_BATCH_EXPORTER_OTLP_HTTP_ENDPOINT=http://custom:4318/v1/traces") - .run( - context -> - assertThat(context) - .getBean(OpenTelemetry.class) - .isNotNull() - .satisfies( - c -> - assertThat(c.toString()) - .contains( - "OtlpHttpSpanExporter{endpoint=http://custom:4318/v1/traces"))); - } - - @Test - void envVarOverrideOtelStyle() { - this.contextRunner - // this is typically set via env var - .withSystemProperties("OTEL_EXPORTER_OTLP_ENDPOINT=http://custom:4318") - .run( - context -> - assertThat(context) - .getBean(OpenTelemetry.class) - .isNotNull() - .satisfies( - c -> - assertThat(c.toString()) - .contains( - "OtlpHttpSpanExporter{endpoint=http://custom:4318/v1/traces"))); - } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml index 455ea6b8d568..e52faf2e594e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml @@ -14,4 +14,16 @@ otel: instrumentation/development: java: foo: - bar: baz + string_key: string_value + string_key_with_env: ${STRING_ENV:default_value} + string_key_with_env_quoted: "${STRING_ENV:default_quoted_value}" + int_key: 42 + int_key_with_env: ${INT_ENV:100} + int_key_with_env_quoted: "${INT_ENV:200}" + bool_key: true + bool_key_with_env: ${BOOL_ENV:false} + bool_key_with_env_quoted: "${BOOL_ENV:true}" + double_key: 3.14 + double_key_with_env: ${DOUBLE_ENV:2.71} + double_key_with_env_quoted: "${DOUBLE_ENV:1.61}" +