diff --git a/api/all/build.gradle.kts b/api/all/build.gradle.kts index 4ad64f607d3..4998fd9ddd3 100644 --- a/api/all/build.gradle.kts +++ b/api/all/build.gradle.kts @@ -18,3 +18,8 @@ dependencies { testImplementation("edu.berkeley.cs.jqf:jqf-fuzz") testImplementation("com.google.guava:guava-testlib") } + +tasks.test { + // Configure environment variable for ConfigUtilTest + environment("CONFIG_KEY", "environment") +} diff --git a/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java b/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java index 43f2c7454bd..82d2e7d42de 100644 --- a/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java +++ b/api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java @@ -5,6 +5,7 @@ package io.opentelemetry.api; +import io.opentelemetry.api.internal.ConfigUtil; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.metrics.MeterBuilder; @@ -44,6 +45,9 @@ @SuppressWarnings("StaticAssignmentOfThrowable") public final class GlobalOpenTelemetry { + private static final String GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY = + "otel.java.global-autoconfigure.enabled"; + private static final Logger logger = Logger.getLogger(GlobalOpenTelemetry.class.getName()); private static final Object mutex = new Object(); @@ -219,15 +223,29 @@ private static OpenTelemetry maybeAutoConfigureAndSetGlobal() { return null; } + // If autoconfigure module is present but global autoconfigure disabled log a warning and return + boolean globalAutoconfigureEnabled = + Boolean.parseBoolean(ConfigUtil.getString(GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY)); + if (!globalAutoconfigureEnabled) { + logger.log( + Level.INFO, + "AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled." + + " To enable, run your JVM with -D" + + GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY + + "=true"); + return null; + } + try { Method initialize = openTelemetrySdkAutoConfiguration.getMethod("initialize"); Object autoConfiguredSdk = initialize.invoke(null); Method getOpenTelemetrySdk = openTelemetrySdkAutoConfiguration.getMethod("getOpenTelemetrySdk"); - return (OpenTelemetry) getOpenTelemetrySdk.invoke(autoConfiguredSdk); + return new ObfuscatedOpenTelemetry( + (OpenTelemetry) getOpenTelemetrySdk.invoke(autoConfiguredSdk)); } catch (NoSuchMethodException | IllegalAccessException e) { throw new IllegalStateException( - "OpenTelemetrySdkAutoConfiguration detected on classpath " + "AutoConfiguredOpenTelemetrySdk detected on classpath " + "but could not invoke initialize method. This is a bug in OpenTelemetry.", e); } catch (InvocationTargetException t) { diff --git a/api/all/src/main/java/io/opentelemetry/api/internal/ConfigUtil.java b/api/all/src/main/java/io/opentelemetry/api/internal/ConfigUtil.java new file mode 100644 index 00000000000..514152b2fcd --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/internal/ConfigUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.internal; + +import java.util.Locale; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Configuration utilities. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class ConfigUtil { + + private ConfigUtil() {} + + /** + * Return the system property or environment variable for the {@code key}. + * + *

Normalize the {@code key} using {@link #normalizePropertyKey(String)}. Match to system + * property keys also normalized with {@link #normalizePropertyKey(String)}. Match to environment + * variable keys normalized with {@link #normalizeEnvironmentVariableKey(String)}. System + * properties take priority over environment variables. + * + * @param key the property key + * @return the system property if not null, or the environment variable if not null, or null + */ + @Nullable + public static String getString(String key) { + String normalizedKey = normalizePropertyKey(key); + String systemProperty = + System.getProperties().entrySet().stream() + .filter(entry -> normalizedKey.equals(normalizePropertyKey(entry.getKey().toString()))) + .map(entry -> entry.getValue().toString()) + .findFirst() + .orElse(null); + if (systemProperty != null) { + return systemProperty; + } + return System.getenv().entrySet().stream() + .filter(entry -> normalizedKey.equals(normalizeEnvironmentVariableKey(entry.getKey()))) + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); + } + + /** + * Normalize an environment variable key by converting to lower case and replacing "_" with ".". + */ + public static String normalizeEnvironmentVariableKey(String key) { + return key.toLowerCase(Locale.ROOT).replace("_", "."); + } + + /** Normalize a property key by converting to lower case and replacing "-" with ".". */ + public static String normalizePropertyKey(String key) { + return key.toLowerCase(Locale.ROOT).replace("-", "."); + } + + /** Returns defaultValue if value is null, otherwise value. This is an internal method. */ + public static T defaultIfNull(@Nullable T value, T defaultValue) { + return value == null ? defaultValue : value; + } +} diff --git a/api/all/src/test/java/io/opentelemetry/api/internal/ConfigUtilTest.java b/api/all/src/test/java/io/opentelemetry/api/internal/ConfigUtilTest.java new file mode 100644 index 00000000000..f08c0dbb78d --- /dev/null +++ b/api/all/src/test/java/io/opentelemetry/api/internal/ConfigUtilTest.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** Relies on environment configuration in {@code ./api/all/build.gradle.kts}. */ +class ConfigUtilTest { + + @Test + @SetSystemProperty(key = "config.key", value = "system") + void getString_SystemPropertyPriority() { + assertThat(ConfigUtil.getString("config.key")).isEqualTo("system"); + assertThat(ConfigUtil.getString("config-key")).isEqualTo("system"); + assertThat(ConfigUtil.getString("other.config.key")).isEqualTo(null); + } + + @Test + @SetSystemProperty(key = "CONFIG-KEY", value = "system") + void getString_SystemPropertyNormalized() { + assertThat(ConfigUtil.getString("config.key")).isEqualTo("system"); + assertThat(ConfigUtil.getString("config-key")).isEqualTo("system"); + assertThat(ConfigUtil.getString("other.config.key")).isEqualTo(null); + } + + @Test + void getString_EnvironmentVariable() { + assertThat(ConfigUtil.getString("config.key")).isEqualTo("environment"); + assertThat(ConfigUtil.getString("other.config.key")).isEqualTo(null); + } + + @Test + void normalizeEnvironmentVariable() { + assertThat(ConfigUtil.normalizeEnvironmentVariableKey("CONFIG_KEY")).isEqualTo("config.key"); + assertThat(ConfigUtil.normalizeEnvironmentVariableKey("config_key")).isEqualTo("config.key"); + assertThat(ConfigUtil.normalizeEnvironmentVariableKey("config-key")).isEqualTo("config-key"); + assertThat(ConfigUtil.normalizeEnvironmentVariableKey("configkey")).isEqualTo("configkey"); + } + + @Test + void normalizePropertyKey() { + assertThat(ConfigUtil.normalizePropertyKey("CONFIG_KEY")).isEqualTo("config_key"); + assertThat(ConfigUtil.normalizePropertyKey("CONFIG.KEY")).isEqualTo("config.key"); + assertThat(ConfigUtil.normalizePropertyKey("config-key")).isEqualTo("config.key"); + assertThat(ConfigUtil.normalizePropertyKey("configkey")).isEqualTo("configkey"); + } + + @Test + void defaultIfnull() { + assertThat(ConfigUtil.defaultIfNull("val1", "val2")).isEqualTo("val1"); + assertThat(ConfigUtil.defaultIfNull(null, "val2")).isEqualTo("val2"); + } +} diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigProperties.java index 030245f2aff..4d03ca108ad 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigProperties.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigProperties.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.autoconfigure.spi; -import static io.opentelemetry.sdk.autoconfigure.spi.ConfigUtil.defaultIfNull; +import static io.opentelemetry.api.internal.ConfigUtil.defaultIfNull; import java.time.Duration; import java.util.List; diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigUtil.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigUtil.java deleted file mode 100644 index eb4c5cc179c..00000000000 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/ConfigUtil.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.autoconfigure.spi; - -import javax.annotation.Nullable; - -/** - * Holder for the non-public defaultIfNull method. This serves only to mitigate the method being on - * a public interface. - */ -final class ConfigUtil { - - /** Returns defaultValue if value is null, otherwise value. This is an internal method. */ - static T defaultIfNull(@Nullable T value, T defaultValue) { - return value == null ? defaultValue : value; - } - - private ConfigUtil() {} -} diff --git a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java index a696f6f4797..c548f7586ca 100644 --- a/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java +++ b/sdk-extensions/autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi/internal/DefaultConfigProperties.java @@ -8,6 +8,7 @@ import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.joining; +import io.opentelemetry.api.internal.ConfigUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import java.time.Duration; @@ -18,7 +19,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -43,7 +43,7 @@ public final class DefaultConfigProperties implements ConfigProperties { * and the {@code defaultProperties}. * *

Environment variables take priority over {@code defaultProperties}. System properties take - * priority over system properties. + * priority over environment variables. */ public static DefaultConfigProperties create(Map defaultProperties) { return new DefaultConfigProperties(System.getProperties(), System.getenv(), defaultProperties); @@ -62,11 +62,13 @@ private DefaultConfigProperties( Map environmentVariables, Map defaultProperties) { Map config = new HashMap<>(); - defaultProperties.forEach((name, value) -> config.put(normalize(name), value)); + defaultProperties.forEach( + (name, value) -> config.put(ConfigUtil.normalizePropertyKey(name), value)); environmentVariables.forEach( - (name, value) -> config.put(name.toLowerCase(Locale.ROOT).replace('_', '.'), value)); + (name, value) -> config.put(ConfigUtil.normalizeEnvironmentVariableKey(name), value)); systemProperties.forEach( - (key, value) -> config.put(normalize(key.toString()), value.toString())); + (key, value) -> + config.put(ConfigUtil.normalizePropertyKey(key.toString()), value.toString())); this.config = config; } @@ -75,7 +77,7 @@ private DefaultConfigProperties( DefaultConfigProperties previousProperties, Map overrides) { // previousProperties are already normalized, they can be copied as they are Map config = new HashMap<>(previousProperties.config); - overrides.forEach((name, value) -> config.put(normalize(name), value)); + overrides.forEach((name, value) -> config.put(ConfigUtil.normalizePropertyKey(name), value)); this.config = config; } @@ -83,13 +85,13 @@ private DefaultConfigProperties( @Override @Nullable public String getString(String name) { - return config.get(normalize(name)); + return config.get(ConfigUtil.normalizePropertyKey(name)); } @Override @Nullable public Boolean getBoolean(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null || value.isEmpty()) { return null; } @@ -100,7 +102,7 @@ public Boolean getBoolean(String name) { @Nullable @SuppressWarnings("UnusedException") public Integer getInt(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null || value.isEmpty()) { return null; } @@ -115,7 +117,7 @@ public Integer getInt(String name) { @Nullable @SuppressWarnings("UnusedException") public Long getLong(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null || value.isEmpty()) { return null; } @@ -130,7 +132,7 @@ public Long getLong(String name) { @Nullable @SuppressWarnings("UnusedException") public Double getDouble(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null || value.isEmpty()) { return null; } @@ -145,7 +147,7 @@ public Double getDouble(String name) { @Nullable @SuppressWarnings("UnusedException") public Duration getDuration(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null || value.isEmpty()) { return null; } @@ -174,7 +176,7 @@ public Duration getDuration(String name) { @Override public List getList(String name) { - String value = config.get(normalize(name)); + String value = config.get(ConfigUtil.normalizePropertyKey(name)); if (value == null) { return Collections.emptyList(); } @@ -188,7 +190,7 @@ public List getList(String name) { * @throws ConfigurationException if {@code name} contains duplicate entries */ public static Set getSet(ConfigProperties config, String name) { - List list = config.getList(normalize(name)); + List list = config.getList(ConfigUtil.normalizePropertyKey(name)); Set set = new HashSet<>(list); if (set.size() != list.size()) { String duplicates = @@ -206,7 +208,7 @@ public static Set getSet(ConfigProperties config, String name) { @Override public Map getMap(String name) { - return getList(normalize(name)).stream() + return getList(ConfigUtil.normalizePropertyKey(name)).stream() .map(keyValuePair -> filterBlanksAndNulls(keyValuePair.split("=", 2))) .map( splitKeyValuePairs -> { @@ -281,8 +283,4 @@ private static String getUnitString(String rawValue) { // Pull everything after the last digit. return rawValue.substring(lastDigitIndex + 1); } - - private static String normalize(String propertyName) { - return propertyName.toLowerCase(Locale.ROOT).replace('-', '.'); - } } diff --git a/sdk-extensions/autoconfigure/src/testConfigError/java/io/opentelemetry/sdk/autoconfigure/ConfigErrorTest.java b/sdk-extensions/autoconfigure/src/testConfigError/java/io/opentelemetry/sdk/autoconfigure/ConfigErrorTest.java index 7e35b32a09f..66e3cc1288f 100644 --- a/sdk-extensions/autoconfigure/src/testConfigError/java/io/opentelemetry/sdk/autoconfigure/ConfigErrorTest.java +++ b/sdk-extensions/autoconfigure/src/testConfigError/java/io/opentelemetry/sdk/autoconfigure/ConfigErrorTest.java @@ -66,6 +66,7 @@ void invalidSampler() { @Test @SetSystemProperty(key = "otel.traces.sampler", value = "traceidratio") @SetSystemProperty(key = "otel.traces.sampler.arg", value = "bar") + @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") @SuppressLogger(GlobalOpenTelemetry.class) void globalOpenTelemetryWhenError() { assertThat(GlobalOpenTelemetry.get()) diff --git a/sdk-extensions/autoconfigure/src/testInitializeRegistersGlobal/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java b/sdk-extensions/autoconfigure/src/testInitializeRegistersGlobal/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java index 5b551b50ffa..02113ad5a10 100644 --- a/sdk-extensions/autoconfigure/src/testInitializeRegistersGlobal/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java +++ b/sdk-extensions/autoconfigure/src/testInitializeRegistersGlobal/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java @@ -7,14 +7,21 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.logs.GlobalLoggerProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.SetSystemProperty; class AutoConfiguredOpenTelemetrySdkTest { + @RegisterExtension + LogCapturer logs = LogCapturer.create().captureForType(GlobalOpenTelemetry.class); + @BeforeEach void setUp() { GlobalOpenTelemetry.resetForTest(); @@ -42,4 +49,24 @@ void initializeAndGet_noGlobal() { // ObfuscatedOpenTelemetry assertThat(GlobalOpenTelemetry.get()).isNotSameAs(sdk); } + + @Test + void globalOpenTelemetry_AutoConfigureDisabled() { + // Autoconfigure is disabled by default and enabled via otel.java.global-autoconfigure.enabled + assertThat(GlobalOpenTelemetry.get()).isSameAs(OpenTelemetry.noop()); + + logs.assertContains( + "AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled." + + " To enable, run your JVM with -Dotel.java.global-autoconfigure.enabled=true"); + } + + @Test + @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") + void globalOpenTelemetry_AutoConfigureEnabled() { + assertThat(GlobalOpenTelemetry.get()) + .extracting("delegate") + .isInstanceOf(OpenTelemetrySdk.class); + + assertThat(logs.getEvents().size()).isEqualTo(0); + } } diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java index b5bbd792612..11f24a73e2f 100644 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java +++ b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpGrpcConfigTest.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.SetSystemProperty; class OtlpGrpcConfigTest { @@ -318,6 +319,7 @@ void configureTlsMissingClientKeyPath() { } @Test + @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") void configuresGlobal() { System.setProperty("otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort()); System.setProperty( diff --git a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java index f9f3bf66d4b..109e384258f 100644 --- a/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java +++ b/sdk-extensions/autoconfigure/src/testOtlp/java/io/opentelemetry/sdk/autoconfigure/OtlpHttpConfigTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.SetSystemProperty; class OtlpHttpConfigTest { @@ -340,6 +341,7 @@ void configureTlsMissingClientKeyPath() { } @Test + @SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true") void configuresGlobal() { System.setProperty("otel.exporter.otlp.protocol", "http/protobuf"); System.setProperty(