GlobalOpenTelemetry trigger of autoconfiguration is opt-in (#5010)

* Do not initialize AutoConfiguredOpenTelemetrySdk in OpenTelemetry.get

* GlobalOpenTelemetry triggers autoconfigure based on env var / system property
This commit is contained in:
jack-berg 2022-12-14 15:10:55 -06:00 committed by GitHub
parent ea51e645dd
commit 7c6f1bd73e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 202 additions and 44 deletions

View File

@ -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")
}

View File

@ -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 @@ import javax.annotation.concurrent.ThreadSafe;
@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 @@ public final class GlobalOpenTelemetry {
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) {

View File

@ -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.
*
* <p>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}.
*
* <p>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> T defaultIfNull(@Nullable T value, T defaultValue) {
return value == null ? defaultValue : value;
}
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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> T defaultIfNull(@Nullable T value, T defaultValue) {
return value == null ? defaultValue : value;
}
private ConfigUtil() {}
}

View File

@ -8,6 +8,7 @@ package io.opentelemetry.sdk.autoconfigure.spi.internal;
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.HashMap;
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}.
*
* <p>Environment variables take priority over {@code defaultProperties}. System properties take
* priority over system properties.
* priority over environment variables.
*/
public static DefaultConfigProperties create(Map<String, String> defaultProperties) {
return new DefaultConfigProperties(System.getProperties(), System.getenv(), defaultProperties);
@ -62,11 +62,13 @@ public final class DefaultConfigProperties implements ConfigProperties {
Map<String, String> environmentVariables,
Map<String, String> defaultProperties) {
Map<String, String> 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 @@ public final class DefaultConfigProperties implements ConfigProperties {
DefaultConfigProperties previousProperties, Map<String, String> overrides) {
// previousProperties are already normalized, they can be copied as they are
Map<String, String> 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 @@ public final class DefaultConfigProperties implements ConfigProperties {
@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 final class DefaultConfigProperties implements ConfigProperties {
@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 final class DefaultConfigProperties implements ConfigProperties {
@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 final class DefaultConfigProperties implements ConfigProperties {
@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 final class DefaultConfigProperties implements ConfigProperties {
@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 final class DefaultConfigProperties implements ConfigProperties {
@Override
public List<String> 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 final class DefaultConfigProperties implements ConfigProperties {
* @throws ConfigurationException if {@code name} contains duplicate entries
*/
public static Set<String> getSet(ConfigProperties config, String name) {
List<String> list = config.getList(normalize(name));
List<String> list = config.getList(ConfigUtil.normalizePropertyKey(name));
Set<String> set = new HashSet<>(list);
if (set.size() != list.size()) {
String duplicates =
@ -206,7 +208,7 @@ public final class DefaultConfigProperties implements ConfigProperties {
@Override
public Map<String, String> 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 @@ public final class DefaultConfigProperties implements ConfigProperties {
// Pull everything after the last digit.
return rawValue.substring(lastDigitIndex + 1);
}
private static String normalize(String propertyName) {
return propertyName.toLowerCase(Locale.ROOT).replace('-', '.');
}
}

View File

@ -66,6 +66,7 @@ class ConfigErrorTest {
@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())

View File

@ -7,14 +7,21 @@ package io.opentelemetry.sdk.autoconfigure;
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 @@ class AutoConfiguredOpenTelemetrySdkTest {
// 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);
}
}

View File

@ -41,6 +41,7 @@ import org.junit.jupiter.api.BeforeEach;
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 @@ class OtlpGrpcConfigTest {
}
@Test
@SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true")
void configuresGlobal() {
System.setProperty("otel.exporter.otlp.endpoint", "https://localhost:" + server.httpsPort());
System.setProperty(

View File

@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterEach;
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 @@ class OtlpHttpConfigTest {
}
@Test
@SetSystemProperty(key = "otel.java.global-autoconfigure.enabled", value = "true")
void configuresGlobal() {
System.setProperty("otel.exporter.otlp.protocol", "http/protobuf");
System.setProperty(