This commit is contained in:
Gregor Zeitlinger 2025-07-27 03:19:28 -07:00 committed by GitHub
commit 8c58123ccd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 425 additions and 0 deletions

View File

@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.incubator.fileconfig;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
public class ConfigPropertiesUtil {
private ConfigPropertiesUtil() {}
/** Resolve {@link ConfigProperties} from the {@code autoConfiguredOpenTelemetrySdk}. */
public static ConfigProperties resolveConfigProperties(
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
ConfigProperties sdkConfigProperties =
AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
if (sdkConfigProperties != null) {
return sdkConfigProperties;
}
ConfigProvider configProvider =
AutoConfigureUtil.getConfigProvider(autoConfiguredOpenTelemetrySdk);
if (configProvider != null) {
DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();
if (instrumentationConfig == null) {
instrumentationConfig = DeclarativeConfigProperties.empty();
}
return new DeclarativeConfigPropertiesBridge(instrumentationConfig);
}
// Should never happen
throw new IllegalStateException(
"AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or DeclarativeConfigProperties. This is likely a programming error in opentelemetry-java");
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.incubator.fileconfig;
import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import javax.annotation.Nullable;
/**
* A {@link ConfigProperties} which resolves properties based on {@link
* DeclarativeConfigProperties}.
*
* <p>Only properties starting with "otel.instrumentation." are resolved. Others return null (or
* default value if provided).
*
* <p>To resolve:
*
* <ul>
* <li>"otel.instrumentation" refers to the ".instrumentation.java" node
* <li>The portion of the property after "otel.instrumentation." is split into segments based on
* ".".
* <li>For each N-1 segment, we walk down the tree to find the relevant leaf {@link
* DeclarativeConfigProperties}.
* <li>We extract the property from the resolved {@link DeclarativeConfigProperties} using the
* last segment as the property key.
* </ul>
*
* <p>For example, given the following YAML, asking for {@code
* ConfigProperties#getString("otel.instrumentation.common.string_key")} yields "value":
*
* <pre>
* instrumentation:
* java:
* common:
* string_key: value
* </pre>
*/
final class DeclarativeConfigPropertiesBridge implements ConfigProperties {
private static final String OTEL_INSTRUMENTATION_PREFIX = "otel.instrumentation.";
// The node at .instrumentation.java
private final DeclarativeConfigProperties instrumentationJavaNode;
DeclarativeConfigPropertiesBridge(DeclarativeConfigProperties instrumentationNode) {
instrumentationJavaNode = instrumentationNode.getStructured("java", empty());
}
@Nullable
@Override
public String getString(String propertyName) {
return getPropertyValue(propertyName, DeclarativeConfigProperties::getString);
}
@Nullable
@Override
public Boolean getBoolean(String propertyName) {
return getPropertyValue(propertyName, DeclarativeConfigProperties::getBoolean);
}
@Nullable
@Override
public Integer getInt(String propertyName) {
return getPropertyValue(propertyName, DeclarativeConfigProperties::getInt);
}
@Nullable
@Override
public Long getLong(String propertyName) {
return getPropertyValue(propertyName, DeclarativeConfigProperties::getLong);
}
@Nullable
@Override
public Double getDouble(String propertyName) {
return getPropertyValue(propertyName, DeclarativeConfigProperties::getDouble);
}
@Nullable
@Override
public Duration getDuration(String propertyName) {
Long millis = getPropertyValue(propertyName, DeclarativeConfigProperties::getLong);
if (millis == null) {
return null;
}
return Duration.ofMillis(millis);
}
@Override
public List<String> getList(String propertyName) {
List<String> propertyValue =
getPropertyValue(
propertyName,
(properties, lastPart) -> properties.getScalarList(lastPart, String.class));
return propertyValue == null ? Collections.emptyList() : propertyValue;
}
@Override
public Map<String, String> getMap(String propertyName) {
DeclarativeConfigProperties propertyValue =
getPropertyValue(propertyName, DeclarativeConfigProperties::getStructured);
if (propertyValue == null) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
propertyValue
.getPropertyKeys()
.forEach(
key -> {
String value = propertyValue.getString(key);
if (value == null) {
return;
}
result.put(key, value);
});
return Collections.unmodifiableMap(result);
}
@Nullable
private <T> T getPropertyValue(
String property, BiFunction<DeclarativeConfigProperties, String, T> extractor) {
if (instrumentationJavaNode == null) {
return null;
}
if (property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
property = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
}
// Split the remainder of the property on "."
String[] segments = property.split("\\.");
if (segments.length == 0) {
return null;
}
// Extract the value by walking to the N-1 entry
DeclarativeConfigProperties target = instrumentationJavaNode;
if (segments.length > 1) {
for (int i = 0; i < segments.length - 1; i++) {
target = target.getStructured(segments[i], empty());
}
}
String lastPart = segments[segments.length - 1];
return extractor.apply(target, lastPart);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.incubator.fileconfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
@SuppressWarnings("DoNotMockAutoValue")
class ConfigPropertiesUtilTest {
@Test
void shouldUseConfigPropertiesForAutoConfiguration() {
ConfigProperties configPropertiesMock = mock(ConfigProperties.class);
AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
try (MockedStatic<AutoConfigureUtil> autoConfigureUtilMock =
Mockito.mockStatic(AutoConfigureUtil.class)) {
autoConfigureUtilMock
.when(() -> AutoConfigureUtil.getConfig(sdkMock))
.thenReturn(configPropertiesMock);
ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
assertThat(configProperties).isSameAs(configPropertiesMock);
}
}
@Test
void shouldUseConfigProviderForDeclarativeConfiguration() {
String propertyName = "testProperty";
String expectedValue = "the value";
DeclarativeConfigProperties javaNodeMock = mock(DeclarativeConfigProperties.class);
when(javaNodeMock.getString(propertyName)).thenReturn(expectedValue);
DeclarativeConfigProperties instrumentationConfigMock = mock(DeclarativeConfigProperties.class);
when(instrumentationConfigMock.getStructured(eq("java"), any())).thenReturn(javaNodeMock);
ConfigProvider configProviderMock = mock(ConfigProvider.class);
when(configProviderMock.getInstrumentationConfig()).thenReturn(instrumentationConfigMock);
AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
try (MockedStatic<AutoConfigureUtil> autoConfigureUtilMock =
Mockito.mockStatic(AutoConfigureUtil.class)) {
autoConfigureUtilMock.when(() -> AutoConfigureUtil.getConfig(sdkMock)).thenReturn(null);
autoConfigureUtilMock
.when(() -> AutoConfigureUtil.getConfigProvider(sdkMock))
.thenReturn(configProviderMock);
ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
assertThat(configProperties.getString(propertyName)).isEqualTo(expectedValue);
}
}
@Test
void shouldUseConfigProviderForDeclarativeConfiguration_noInstrumentationConfig() {
AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
ConfigProvider configProviderMock = mock(ConfigProvider.class);
when(configProviderMock.getInstrumentationConfig()).thenReturn(null);
try (MockedStatic<AutoConfigureUtil> autoConfigureUtilMock =
Mockito.mockStatic(AutoConfigureUtil.class)) {
autoConfigureUtilMock.when(() -> AutoConfigureUtil.getConfig(sdkMock)).thenReturn(null);
autoConfigureUtilMock
.when(() -> AutoConfigureUtil.getConfigProvider(sdkMock))
.thenReturn(configProviderMock);
ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
assertThat(configProperties.getString("testProperty")).isEqualTo(null);
}
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.incubator.fileconfig;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.InstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class DeclarativeConfigPropertiesBridgeTest {
private static final String YAML =
"file_format: 0.4\n"
+ "instrumentation/development:\n"
+ " java:\n"
+ " common:\n"
+ " default-enabled: true\n"
+ " runtime-telemetry:\n"
+ " enabled: false\n"
+ " example-instrumentation:\n"
+ " string_key: value\n"
+ " bool_key: true\n"
+ " int_key: 1\n"
+ " double_key: 1.1\n"
+ " list_key:\n"
+ " - value1\n"
+ " - value2\n"
+ " - true\n"
+ " map_key:\n"
+ " string_key1: value1\n"
+ " string_key2: value2\n"
+ " bool_key: true\n"
+ " acme:\n"
+ " full_name:\n"
+ " preserved: true";
private ConfigProperties bridge;
private ConfigProperties emptyBridge;
@BeforeEach
void setup() {
OpenTelemetryConfigurationModel model =
DeclarativeConfiguration.parse(
new ByteArrayInputStream(YAML.getBytes(StandardCharsets.UTF_8)));
SdkConfigProvider configProvider = SdkConfigProvider.create(model);
bridge =
new DeclarativeConfigPropertiesBridge(
Objects.requireNonNull(configProvider.getInstrumentationConfig()));
OpenTelemetryConfigurationModel emptyModel =
new OpenTelemetryConfigurationModel()
.withAdditionalProperty("instrumentation/development", new InstrumentationModel());
SdkConfigProvider emptyConfigProvider = SdkConfigProvider.create(emptyModel);
emptyBridge =
new DeclarativeConfigPropertiesBridge(
Objects.requireNonNull(emptyConfigProvider.getInstrumentationConfig()));
}
@Test
void getProperties() {
// only properties starting with "otel.instrumentation." are resolved
// asking for properties which don't exist or inaccessible shouldn't result in an error
assertThat(bridge.getString("file_format")).isNull();
assertThat(bridge.getString("file_format", "foo")).isEqualTo("foo");
assertThat(emptyBridge.getBoolean("otel.instrumentation.common.default-enabled")).isNull();
assertThat(emptyBridge.getBoolean("otel.instrumentation.common.default-enabled", true))
.isTrue();
// common cases
assertThat(bridge.getBoolean("otel.instrumentation.common.default-enabled")).isTrue();
assertThat(bridge.getBoolean("otel.instrumentation.runtime-telemetry.enabled")).isFalse();
// check all the types
Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("string_key1", "value1");
expectedMap.put("string_key2", "value2");
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.string_key"))
.isEqualTo("value");
assertThat(bridge.getBoolean("otel.instrumentation.example-instrumentation.bool_key")).isTrue();
assertThat(bridge.getInt("otel.instrumentation.example-instrumentation.int_key")).isEqualTo(1);
assertThat(bridge.getLong("otel.instrumentation.example-instrumentation.int_key"))
.isEqualTo(1L);
assertThat(bridge.getDuration("otel.instrumentation.example-instrumentation.int_key"))
.isEqualTo(Duration.ofMillis(1));
assertThat(bridge.getDouble("otel.instrumentation.example-instrumentation.double_key"))
.isEqualTo(1.1);
assertThat(bridge.getList("otel.instrumentation.example-instrumentation.list_key"))
.isEqualTo(Arrays.asList("value1", "value2"));
assertThat(bridge.getMap("otel.instrumentation.example-instrumentation.map_key"))
.isEqualTo(expectedMap);
// asking for properties with the wrong type returns null
assertThat(bridge.getBoolean("otel.instrumentation.example-instrumentation.string_key"))
.isNull();
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.bool_key")).isNull();
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.int_key")).isNull();
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.double_key"))
.isNull();
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.list_key")).isNull();
assertThat(bridge.getString("otel.instrumentation.example-instrumentation.map_key")).isNull();
// check all the types
assertThat(bridge.getString("otel.instrumentation.other-instrumentation.string_key", "value"))
.isEqualTo("value");
assertThat(bridge.getBoolean("otel.instrumentation.other-instrumentation.bool_key", true))
.isTrue();
assertThat(bridge.getInt("otel.instrumentation.other-instrumentation.int_key", 1)).isEqualTo(1);
assertThat(bridge.getLong("otel.instrumentation.other-instrumentation.int_key", 1L))
.isEqualTo(1L);
assertThat(
bridge.getDuration(
"otel.instrumentation.other-instrumentation.int_key", Duration.ofMillis(1)))
.isEqualTo(Duration.ofMillis(1));
assertThat(bridge.getDouble("otel.instrumentation.other-instrumentation.double_key", 1.1))
.isEqualTo(1.1);
assertThat(
bridge.getList(
"otel.instrumentation.other-instrumentation.list_key",
Arrays.asList("value1", "value2")))
.isEqualTo(Arrays.asList("value1", "value2"));
assertThat(bridge.getMap("otel.instrumentation.other-instrumentation.map_key", expectedMap))
.isEqualTo(expectedMap);
// verify vendor specific property names are preserved in unchanged form (prefix is not stripped
// as for otel.instrumentation.*)
assertThat(bridge.getBoolean("acme.full_name.preserved")).isTrue();
}
}