Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
08e70a0
add system properties bridge
zeitlinger Nov 17, 2025
5c1638f
add system properties bridge
zeitlinger Nov 17, 2025
f503dbe
add tests
zeitlinger Nov 17, 2025
246fc71
add list
zeitlinger Nov 18, 2025
add113a
refactor
zeitlinger Nov 18, 2025
a0525ac
refactor
zeitlinger Nov 18, 2025
bca6ff1
string
zeitlinger Nov 18, 2025
556b9c3
string
zeitlinger Nov 18, 2025
1109153
string
zeitlinger Nov 18, 2025
5dba36a
string
zeitlinger Nov 18, 2025
3bccdf7
string
zeitlinger Nov 18, 2025
652eb54
string
zeitlinger Nov 18, 2025
06e76b0
string
zeitlinger Nov 18, 2025
d7d11a2
string
zeitlinger Nov 18, 2025
894b836
javadoc
zeitlinger Nov 18, 2025
f25fcd5
add contract
zeitlinger Nov 18, 2025
acc63dd
add contract
zeitlinger Nov 18, 2025
55f7cd0
remove contract
zeitlinger Nov 18, 2025
1ea8025
remove contract
zeitlinger Nov 18, 2025
2e81251
inline method to avoid class not found exception for DeclarativeConfi…
zeitlinger Nov 18, 2025
4ac86c4
pr review
zeitlinger Nov 25, 2025
17d3014
use optional instead of providing default for boolean
zeitlinger Nov 25, 2025
2897e57
use optional instead of providing default for int
zeitlinger Nov 25, 2025
ce07aa3
use optional instead of providing default for list
zeitlinger Nov 25, 2025
83e23eb
use optional instead of providing default for string
zeitlinger Nov 25, 2025
2b5e2c4
fix
zeitlinger Nov 25, 2025
fdbee40
fix
zeitlinger Nov 25, 2025
aab3eed
fix
zeitlinger Nov 25, 2025
0f400ab
fix
zeitlinger Nov 25, 2025
3fd7b6f
split pr
zeitlinger Nov 27, 2025
18d719d
split pr
zeitlinger Nov 27, 2025
3b42108
Merge branch 'main' into system-properties-bridge
zeitlinger Nov 27, 2025
7c09f40
fix
zeitlinger Nov 27, 2025
55146b8
fix
zeitlinger Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions instrumentation-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
testImplementation("io.opentelemetry:opentelemetry-exporter-common")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
testImplementation("org.junit-pioneer:junit-pioneer")

jmhImplementation(project(":instrumentation-api-incubator"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {

private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName());

private static final SpanSuppressionStrategy spanSuppressionStrategy =
SpanSuppressionStrategy.fromConfig(
ConfigPropertiesUtil.getString(
"otel.instrumentation.experimental.span-suppression-strategy"));

final OpenTelemetry openTelemetry;
final String instrumentationName;
SpanNameExtractor<? super REQUEST> spanNameExtractor;
Expand Down Expand Up @@ -373,8 +368,17 @@ private String getSchemaUrl() {
}

SpanSuppressor buildSpanSuppressor() {
// otel.instrumentation.experimental.* doesn't fit the usual pattern of configuration properties
// for instrumentations, so we need to handle both declarative and non-declarative configs here
String value =
ConfigPropertiesUtil.isDeclarativeConfig(openTelemetry)
? ConfigPropertiesUtil.getString(
openTelemetry, "common", "experimental", "span_suppression_strategy")
.orElse(null)
: ConfigPropertiesUtil.getString(
"otel.instrumentation.experimental.span-suppression-strategy");
return new SpanSuppressors.ByContextKey(
spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()));
SpanSuppressionStrategy.fromConfig(value).create(getSpanKeysFromAttributesExtractors()));
}

private Set<SpanKey> getSpanKeysFromAttributesExtractors() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@

package io.opentelemetry.instrumentation.api.internal;

import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
import static java.util.Collections.emptyList;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

Expand All @@ -17,11 +25,51 @@
*/
public final class ConfigPropertiesUtil {

private static final boolean supportsDeclarativeConfig = supportsDeclarativeConfig();

private static boolean supportsDeclarativeConfig() {
try {
Class.forName("io.opentelemetry.api.incubator.ExtendedOpenTelemetry");
return true;
} catch (ClassNotFoundException e) {
// The incubator module is not available.
// This only happens in OpenTelemetry API instrumentation tests, where an older version of
// OpenTelemetry API is used that does not have ExtendedOpenTelemetry.
// Having the incubator module without ExtendedOpenTelemetry class should still return false
// for those tests to avoid a ClassNotFoundException.
Comment on lines +36 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only happens in OpenTelemetry API instrumentation tests

these are tests of OpenTelemetry API in the users class loader, so not following how that affects this class which is in the agent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without the check, we get tests where the incubator exists, but is too old to have ExtendedOpenTelemetry:

gradle :instrumentation:opentelemetry-api:opentelemetry-api-1.42:javaagent:incubatorTest

	at io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil.isDeclarativeConfig(ConfigPropertiesUtil.java:116)
	at io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder.buildSpanSuppressor(InstrumenterBuilder.java:374)
	at io.opentelemetry.instrumentation.api.instrumenter.Instrumenter.<init>(Instrumenter.java:105)
	at io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder.buildInstrumenter(InstrumenterBuilder.java:298)
	at io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder.buildInstrumenter(InstrumenterBuilder.java:287)
	at io.opentelemetry.instrumentation.testing.TestInstrumenters.<init>(TestInstrumenters.java:41)
	at io.opentelemetry.instrumentation.testing.InstrumentationTestRunner.<init>(InstrumentationTestRunner.java:68)
	at io.opentelemetry.instrumentation.testing.AgentTestRunner.<init>(AgentTestRunner.java:47)
	at io.opentelemetry.instrumentation.testing.AgentTestRunner.<clinit>(AgentTestRunner.java:40)
	at io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension.<init>(AgentInstrumentationExtension.java:35)
	at io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension.create(AgentInstrumentationExtension.java:39)
	at io.opentelemetry.javaagent.instrumentation.opentelemetryapi.v1_42.incubator.logs.LoggerTest.<clinit>(LoggerTest.java:41)
	at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized0(Native Method)
	at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized(Unsafe.java:1160)
	at java.base/java.lang.reflect.Field.acquireOverrideFieldAccessor(Field.java:1200)
	at java.base/java.lang.reflect.Field.getOverrideFieldAccessor(Field.java:1169)
	at java.base/java.lang.reflect.Field.get(Field.java:444)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
	at java.base/java.util.stream.DistinctOps$1$2.end(DistinctOps.java:168)
	at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:261)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.ClassNotFoundException: io.opentelemetry.api.incubator.ExtendedOpenTelemetry
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	... 29 more

return false;
}
}

/**
* Returns the boolean value of the given property name from system properties and environment
* variables.
*
* <p>It's recommended to use {@link #getBoolean(OpenTelemetry, String...)} instead to support
* Declarative Config.
*/
public static boolean getBoolean(String propertyName, boolean defaultValue) {
String strValue = getString(propertyName);
return strValue == null ? defaultValue : Boolean.parseBoolean(strValue);
}

/**
* Returns the boolean value of the given property name from Declarative Config if available,
* otherwise falls back to system properties and environment variables.
*/
public static Optional<Boolean> getBoolean(OpenTelemetry openTelemetry, String... propertyName) {
DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
if (node != null) {
return Optional.ofNullable(node.getBoolean(leaf(propertyName)));
}
String strValue = getString(toSystemProperty(propertyName));
return strValue == null ? Optional.empty() : Optional.of(Boolean.parseBoolean(strValue));
}

/**
* Returns the int value of the given property name from system properties and environment
* variables.
*/
public static int getInt(String propertyName, int defaultValue) {
String strValue = getString(propertyName);
if (strValue == null) {
Expand All @@ -34,6 +82,13 @@ public static int getInt(String propertyName, int defaultValue) {
}
}

/**
* Returns the string value of the given property name from system properties and environment
* variables.
*
* <p>It's recommended to use {@link #getString(OpenTelemetry, String...)} instead to support
* Declarative Config.
*/
@Nullable
public static String getString(String propertyName) {
String value = System.getProperty(propertyName);
Expand All @@ -43,17 +98,35 @@ public static String getString(String propertyName) {
return System.getenv(toEnvVarName(propertyName));
}

public static String getString(String propertyName, String defaultValue) {
String strValue = getString(propertyName);
return strValue == null ? defaultValue : strValue;
/**
* Returns the string value of the given property name from Declarative Config if available,
* otherwise falls back to system properties and environment variables.
*/
public static Optional<String> getString(OpenTelemetry openTelemetry, String... propertyName) {
DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
if (node != null) {
return Optional.ofNullable(node.getString(leaf(propertyName)));
}
return Optional.ofNullable(getString(toSystemProperty(propertyName)));
}

public static List<String> getList(String propertyName, List<String> defaultValue) {
String value = getString(propertyName);
if (value == null) {
return defaultValue;
/**
* Returns the list of strings value of the given property name from Declarative Config if
* available, otherwise falls back to system properties and environment variables.
*/
public static List<String> getList(OpenTelemetry openTelemetry, String... propertyName) {
DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
if (node != null) {
return node.getScalarList(leaf(propertyName), String.class, emptyList());
}
return filterBlanksAndNulls(value.split(","));
return Optional.ofNullable(getString(toSystemProperty(propertyName)))
.map(value -> filterBlanksAndNulls(value.split(",")))
.orElse(emptyList());
}

/** Returns true if the given OpenTelemetry instance supports Declarative Config. */
public static boolean isDeclarativeConfig(OpenTelemetry openTelemetry) {
return supportsDeclarativeConfig && openTelemetry instanceof ExtendedOpenTelemetry;
}

private static List<String> filterBlanksAndNulls(String[] values) {
Expand All @@ -67,5 +140,33 @@ private static String toEnvVarName(String propertyName) {
return propertyName.toUpperCase(Locale.ROOT).replace('-', '_').replace('.', '_');
}

private static String leaf(String[] propertyName) {
return propertyName[propertyName.length - 1];
}

@Nullable
private static DeclarativeConfigProperties getDeclarativeConfigNode(
OpenTelemetry openTelemetry, String... propertyName) {
if (isDeclarativeConfig(openTelemetry)) {
ExtendedOpenTelemetry extendedOpenTelemetry = (ExtendedOpenTelemetry) openTelemetry;
ConfigProvider configProvider = extendedOpenTelemetry.getConfigProvider();
DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();
if (instrumentationConfig == null) {
return empty();
}
DeclarativeConfigProperties node = instrumentationConfig.getStructured("java", empty());
// last part is the leaf property
for (int i = 0; i < propertyName.length - 1; i++) {
node = node.getStructured(propertyName[i], empty());
}
return node;
}
return null;
}

public static String toSystemProperty(String[] propertyName) {
return "otel.instrumentation." + String.join(".", propertyName).replace('_', '-');
}

private ConfigPropertiesUtil() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@

package io.opentelemetry.instrumentation.api.internal;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationBuilder;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.InstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.junitpioneer.jupiter.SetSystemProperty;

Expand All @@ -31,6 +46,48 @@ void getString_none() {
assertThat(ConfigPropertiesUtil.getString("test.property.string")).isNull();
}

@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_STRING", value = "env_value")
@SetSystemProperty(key = "otel.instrumentation.test.property.string", value = "sys_value")
@Test
void getString_withOpenTelemetry_systemProperty() {
assertString("sys_value");
}

@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_STRING", value = "env_value")
@Test
void getString_withOpenTelemetry_environmentVariable() {
assertString("env_value");
}

@Test
void getString_withOpenTelemetry_none() {
assertString("default_value");
}

private static void assertString(String expected) {
assertThat(
ConfigPropertiesUtil.getString(OpenTelemetry.noop(), "test", "property", "string")
.orElse("default_value"))
.isEqualTo(expected);
}

public static Stream<Arguments> stringValuesProvider() {
return Stream.of(
Arguments.of("value1", "value1"),
Arguments.of("", ""),
Arguments.of(null, "default_value"),
Arguments.of(123, "default_value"), // no type coercion in declarative config
Arguments.of(true, "default_value")); // no type coercion in declarative config
}

@ParameterizedTest
@MethodSource("stringValuesProvider")
void getString_declarativeConfig(Object property, String expected) {
OpenTelemetry openTelemetry = DeclarativeConfiguration.create(model(property));
assertThat(ConfigPropertiesUtil.getString(openTelemetry, "foo", "bar").orElse("default_value"))
.isEqualTo(expected);
}

@SetEnvironmentVariable(key = "TEST_PROPERTY_INT", value = "12")
@SetSystemProperty(key = "test.property.int", value = "42")
@Test
Expand All @@ -55,21 +112,109 @@ void getInt_invalidNumber() {
assertThat(ConfigPropertiesUtil.getInt("test.property.int", -1)).isEqualTo(-1);
}

@SetEnvironmentVariable(key = "TEST_PROPERTY_BOOLEAN", value = "false")
@SetSystemProperty(key = "test.property.boolean", value = "true")
@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_BOOLEAN", value = "false")
@SetSystemProperty(key = "otel.instrumentation.test.property.boolean", value = "true")
@Test
void getBoolean_systemProperty() {
assertThat(ConfigPropertiesUtil.getBoolean("test.property.boolean", false)).isTrue();
assertBoolean(true);
}

@SetEnvironmentVariable(key = "TEST_PROPERTY_BOOLEAN", value = "true")
@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_BOOLEAN", value = "true")
@Test
void getBoolean_environmentVariable() {
assertThat(ConfigPropertiesUtil.getBoolean("test.property.boolean", false)).isTrue();
assertBoolean(true);
}

@Test
void getBoolean_none() {
assertThat(ConfigPropertiesUtil.getBoolean("test.property.boolean", false)).isFalse();
assertBoolean(false);
}

private static void assertBoolean(boolean expected) {
assertThat(ConfigPropertiesUtil.getBoolean("otel.instrumentation.test.property.boolean", false))
.isEqualTo(expected);
assertThat(
ConfigPropertiesUtil.getBoolean(OpenTelemetry.noop(), "test", "property", "boolean")
.orElse(false))
.isEqualTo(expected);
}

public static Stream<Arguments> booleanValuesProvider() {
return Stream.of(
Arguments.of(true, true),
Arguments.of(false, false),
Arguments.of("invalid", false),
Arguments.of("true", false), // no type coercion in declarative config
Arguments.of(null, false));
}

@ParameterizedTest
@MethodSource("booleanValuesProvider")
void getBoolean_declarativeConfig(Object property, boolean expected) {
assertThat(
ConfigPropertiesUtil.getBoolean(
DeclarativeConfiguration.create(model(property)), "foo", "bar")
.orElse(false))
.isEqualTo(expected);
}

private static OpenTelemetryConfigurationModel model(Object value) {
return new DeclarativeConfigurationBuilder()
.customizeModel(
new OpenTelemetryConfigurationModel()
.withFileFormat("1.0-rc.1")
.withInstrumentationDevelopment(
new InstrumentationModel()
.withJava(
new ExperimentalLanguageSpecificInstrumentationModel()
.withAdditionalProperty(
"foo", Collections.singletonMap("bar", value)))));
}

@Test
void toSystemProperty() {
assertThat(ConfigPropertiesUtil.toSystemProperty(new String[] {"a_b", "c", "d"}))
.isEqualTo("otel.instrumentation.a-b.c.d");
}

@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_LIST", value = "a,b,c")
@SetSystemProperty(key = "otel.instrumentation.test.property.list", value = "x,y,z")
@Test
void getList_systemProperty() {
assertList(asList("x", "y", "z"));
}

@SetEnvironmentVariable(key = "OTEL_INSTRUMENTATION_TEST_PROPERTY_LIST", value = "a,b,c")
@Test
void getList_environmentVariable() {
assertList(asList("a", "b", "c"));
}

@Test
void getList_none() {
assertList(emptyList());
}

private static void assertList(List<String> expected) {
assertThat(ConfigPropertiesUtil.getList(OpenTelemetry.noop(), "test", "property", "list"))
.isEqualTo(expected);
}

public static Stream<Arguments> listValuesProvider() {
return Stream.of(
Arguments.of(asList("a", "b", "c"), asList("a", "b", "c")),
Arguments.of(singletonList("single"), singletonList("single")),
Arguments.of(emptyList(), emptyList()),
Arguments.of("invalid", emptyList()),
Arguments.of(null, emptyList()));
}

@ParameterizedTest
@MethodSource("listValuesProvider")
void getList_declarativeConfig(Object property, List<String> expected) {
assertThat(
ConfigPropertiesUtil.getList(
DeclarativeConfiguration.create(model(property)), "foo", "bar"))
.isEqualTo(expected);
}
}
Loading
Loading