add support for missing list properties in spring starter (#12434)

Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
This commit is contained in:
Gregor Zeitlinger 2024-10-16 17:00:57 +02:00 committed by GitHub
parent 780cdf4a93
commit 9e83898f0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 386 additions and 51 deletions

View File

@ -1,2 +1,5 @@
Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.9.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.8.0.jar
No changes.
=== UNCHANGED CLASS: PUBLIC io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
*** MODIFIED ANNOTATION: org.springframework.boot.context.properties.EnableConfigurationProperties
*** MODIFIED ELEMENT: value=io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties (<- io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties,io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.PropagationProperties)

View File

@ -10,8 +10,8 @@ import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.MapConverter;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.PropagationProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.DistroVersionResourceProvider;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.SpringResourceProvider;
@ -51,7 +51,7 @@ import org.springframework.core.env.Environment;
@EnableConfigurationProperties({
OtlpExporterProperties.class,
OtelResourceProperties.class,
PropagationProperties.class
OtelSpringProperties.class
})
public class OpenTelemetryAutoConfiguration {
@ -90,7 +90,7 @@ public class OpenTelemetryAutoConfiguration {
Environment env,
OtlpExporterProperties otlpExporterProperties,
OtelResourceProperties resourceProperties,
PropagationProperties propagationProperties,
OtelSpringProperties otelSpringProperties,
OpenTelemetrySdkComponentLoader componentLoader) {
return AutoConfigureUtil.setComponentLoader(
@ -101,7 +101,7 @@ public class OpenTelemetryAutoConfiguration {
env,
otlpExporterProperties,
resourceProperties,
propagationProperties,
otelSpringProperties,
c)),
componentLoader)
.build();

View File

@ -0,0 +1,243 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
// yaml lists only work if you create a @ConfigurationProperties object
@ConfigurationProperties(prefix = "otel")
public final class OtelSpringProperties {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static final class Java {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public static final class Enabled {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can
* change at any time.
*/
public static final class Resource {
private List<String> providers = Collections.emptyList();
public List<String> getProviders() {
return providers;
}
public void setProviders(List<String> providers) {
this.providers = providers;
}
}
private Enabled.Resource resource = new Enabled.Resource();
public Enabled.Resource getResource() {
return resource;
}
public void setResource(Enabled.Resource resource) {
this.resource = resource;
}
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public static final class Disabled {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can
* change at any time.
*/
public static final class Resource {
private List<String> providers = Collections.emptyList();
public List<String> getProviders() {
return providers;
}
public void setProviders(List<String> providers) {
this.providers = providers;
}
}
private Disabled.Resource resource = new Disabled.Resource();
public Disabled.Resource getResource() {
return resource;
}
public void setResource(Disabled.Resource resource) {
this.resource = resource;
}
}
private Enabled enabled = new Enabled();
private Java.Disabled disabled = new Java.Disabled();
public Enabled getEnabled() {
return enabled;
}
public void setEnabled(Enabled enabled) {
this.enabled = enabled;
}
public Java.Disabled getDisabled() {
return disabled;
}
public void setDisabled(Java.Disabled disabled) {
this.disabled = disabled;
}
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public static final class Experimental {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public static final class Metrics {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can
* change at any time.
*/
public static final class View {
private List<String> config = Collections.emptyList();
public List<String> getConfig() {
return config;
}
public void setConfig(List<String> config) {
this.config = config;
}
}
private View view = new View();
public View getView() {
return view;
}
public void setView(View view) {
this.view = view;
}
}
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public static final class Resource {
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can
* change at any time.
*/
public static final class Disabled {
private List<String> keys = Collections.emptyList();
public List<String> getKeys() {
return keys;
}
public void setKeys(List<String> keys) {
this.keys = keys;
}
}
private Resource.Disabled disabled = new Resource.Disabled();
public Resource.Disabled getDisabled() {
return disabled;
}
public void setDisabled(Resource.Disabled disabled) {
this.disabled = disabled;
}
}
private Metrics metrics = new Metrics();
private Resource resource = new Resource();
public Metrics getMetrics() {
return metrics;
}
public void setMetrics(Metrics metrics) {
this.metrics = metrics;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
}
private List<String> propagators = Collections.emptyList();
private Java java = new Java();
private Experimental experimental = new Experimental();
public List<String> getPropagators() {
return propagators;
}
public void setPropagators(List<String> propagators) {
this.propagators = propagators;
}
public Java getJava() {
return java;
}
public void setJava(Java java) {
this.java = java;
}
public Experimental getExperimental() {
return experimental;
}
public void setExperimental(Experimental experimental) {
this.experimental = experimental;
}
public List<String> getJavaEnabledResourceProviders() {
return java.getEnabled().getResource().getProviders();
}
public List<String> getJavaDisabledResourceProviders() {
return java.getDisabled().getResource().getProviders();
}
public List<String> getExperimentalMetricsViewConfig() {
return experimental.getMetrics().getView().getConfig();
}
public List<String> getExperimentalResourceDisabledKeys() {
return experimental.getResource().getDisabled().getKeys();
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
@ConfigurationProperties(prefix = "otel")
public final class PropagationProperties {
private List<String> propagators = Collections.emptyList();
public List<String> getPropagators() {
return propagators;
}
public void setPropagators(List<String> propagators) {
this.propagators = propagators;
}
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.propertie
import io.opentelemetry.api.internal.ConfigUtil;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.time.Duration;
@ -29,22 +30,75 @@ public class SpringConfigProperties implements ConfigProperties {
private final ExpressionParser parser;
private final OtlpExporterProperties otlpExporterProperties;
private final OtelResourceProperties resourceProperties;
private final PropagationProperties propagationProperties;
private final ConfigProperties otelSdkProperties;
private final ConfigProperties customizedListProperties;
private final Map<String, List<String>> listPropertyValues = new HashMap<>();
static final String DISABLED_KEY = "otel.java.disabled.resource.providers";
static final String ENABLED_KEY = "otel.java.enabled.resource.providers";
public SpringConfigProperties(
Environment environment,
ExpressionParser parser,
OtlpExporterProperties otlpExporterProperties,
OtelResourceProperties resourceProperties,
PropagationProperties propagationProperties,
OtelSpringProperties otelSpringProperties,
ConfigProperties otelSdkProperties) {
this.environment = environment;
this.parser = parser;
this.otlpExporterProperties = otlpExporterProperties;
this.resourceProperties = resourceProperties;
this.propagationProperties = propagationProperties;
this.otelSdkProperties = otelSdkProperties;
this.customizedListProperties =
createCustomizedListProperties(otelSdkProperties, otelSpringProperties);
listPropertyValues.put(ENABLED_KEY, otelSpringProperties.getJavaEnabledResourceProviders());
listPropertyValues.put(DISABLED_KEY, otelSpringProperties.getJavaDisabledResourceProviders());
listPropertyValues.put(
"otel.experimental.metrics.view.config",
otelSpringProperties.getExperimentalMetricsViewConfig());
listPropertyValues.put(
"otel.experimental.resource.disabled.keys",
otelSpringProperties.getExperimentalResourceDisabledKeys());
listPropertyValues.put("otel.propagators", otelSpringProperties.getPropagators());
}
private static Map<String, String> createMapForListProperty(
String key, List<String> springList, ConfigProperties configProperties) {
if (!springList.isEmpty()) {
return Collections.singletonMap(key, String.join(",", springList));
} else {
String otelList = configProperties.getString(key);
if (otelList != null) {
return Collections.singletonMap(key, otelList);
}
}
return Collections.emptyMap();
}
private static ConfigProperties createCustomizedListProperties(
ConfigProperties configProperties, OtelSpringProperties otelSpringProperties) {
// io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizer
// has already been applied before this point, so we have to apply the same logic here
// the logic is implemented here:
// https://github.com/open-telemetry/opentelemetry-java/blob/325822ce8527b83a09274c86a5123a214db80c1d/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java#L634-L641
// ResourceProviderPropertiesCustomizer gets applied by "propertiesCustomizers"
// and spring properties by "configPropertiesCustomizer", which is later
Map<String, String> map =
new HashMap<>(
createMapForListProperty(
ENABLED_KEY,
otelSpringProperties.getJavaEnabledResourceProviders(),
configProperties));
map.putAll(
createMapForListProperty(
DISABLED_KEY,
otelSpringProperties.getJavaDisabledResourceProviders(),
configProperties));
return DefaultConfigProperties.createFromMap(
new ResourceProviderPropertiesCustomizer()
.customize(DefaultConfigProperties.createFromMap(map)));
}
// visible for testing
@ -52,14 +106,14 @@ public class SpringConfigProperties implements ConfigProperties {
Environment env,
OtlpExporterProperties otlpExporterProperties,
OtelResourceProperties resourceProperties,
PropagationProperties propagationProperties,
OtelSpringProperties otelSpringProperties,
ConfigProperties fallback) {
return new SpringConfigProperties(
env,
new SpelExpressionParser(),
otlpExporterProperties,
resourceProperties,
propagationProperties,
otelSpringProperties,
fallback);
}
@ -114,8 +168,13 @@ public class SpringConfigProperties implements ConfigProperties {
String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name);
if (normalizedName.equals("otel.propagators")) {
return propagationProperties.getPropagators();
List<String> list = listPropertyValues.get(normalizedName);
if (list != null) {
List<String> c = customizedListProperties.getList(name);
if (!c.isEmpty()) {
return c;
}
return list;
}
return or(environment.getProperty(normalizedName, List.class), otelSdkProperties.getList(name));

View File

@ -76,6 +76,11 @@
"description": "If set, configure experimental cardinality limit. The value dictates the maximum number of distinct points per metric.",
"defaultValue": 2000
},
{
"name": "otel.experimental.metrics.view.config",
"type": "java.util.List<java.lang.String>",
"description": "View file configuration See https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#view-file-configuration."
},
{
"name": "otel.experimental.resource.disabled.keys",
"type": "java.util.List<java.lang.String>",

View File

@ -12,6 +12,7 @@ import static org.mockito.Mockito.withSettings;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.junit.jupiter.api.DisplayName;
@ -35,7 +36,12 @@ class OpenTelemetryAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withPropertyValues(
"otel.traces.exporter=none", "otel.metrics.exporter=none", "otel.logs.exporter=none");
"otel.traces.exporter=none",
"otel.metrics.exporter=none",
"otel.logs.exporter=none",
"otel.propagators=b3",
"otel.experimental.resource.disabled.keys=a,b",
"otel.java.disabled.resource.providers=d");
@Test
@DisplayName(
@ -61,6 +67,54 @@ class OpenTelemetryAutoConfigurationTest {
.run(context -> assertThat(context).hasBean("openTelemetry").hasBean("otelProperties"));
}
@Test
void specialListProperties() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
.run(
context ->
assertThat(context)
.getBean("otelProperties")
.satisfies(
p -> {
ConfigProperties configProperties = (ConfigProperties) p;
assertThat(configProperties.getList("otel.propagators"))
.containsExactly("b3");
assertThat(
configProperties.getList(
"otel.experimental.resource.disabled.keys"))
.containsExactly("a", "b");
assertThat(
configProperties.getList("otel.java.disabled.resource.providers"))
.containsExactlyInAnyOrder(
"d",
"io.opentelemetry.contrib.aws.resource.BeanstalkResourceProvider",
"io.opentelemetry.contrib.aws.resource.Ec2ResourceProvider",
"io.opentelemetry.contrib.aws.resource.EcsResourceProvider",
"io.opentelemetry.contrib.aws.resource.EksResourceProvider",
"io.opentelemetry.contrib.aws.resource.LambdaResourceProvider",
"io.opentelemetry.contrib.gcp.resource.GCPResourceProvider",
"io.opentelemetry.instrumentation.resources.ResourceProviderPropertiesCustomizerTest$Provider");
}));
}
@Test
void enabledProviders() {
this.contextRunner
.withPropertyValues("otel.java.enabled.resource.providers=e1,e2")
.withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class))
.run(
context ->
assertThat(context)
.getBean("otelProperties", ConfigProperties.class)
.satisfies(
configProperties ->
assertThat(
configProperties.getList(
"otel.java.enabled.resource.providers"))
.containsExactly("e1", "e2")));
}
@Test
@DisplayName(
"when Application Context DOES NOT contain OpenTelemetry bean but SpanExporter should initialize openTelemetry")

View File

@ -92,7 +92,7 @@ class OtlpExporterPropertiesTest {
new SpelExpressionParser(),
context.getBean(OtlpExporterProperties.class),
new OtelResourceProperties(),
new PropagationProperties(),
new OtelSpringProperties(),
DefaultConfigProperties.createFromMap(Collections.emptyMap()));
}
}

View File

@ -10,8 +10,8 @@ import static org.assertj.core.api.Assertions.entry;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.PropagationProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.util.HashMap;
@ -51,7 +51,7 @@ class SpringConfigPropertiesTest {
new SpelExpressionParser(),
context.getBean(OtlpExporterProperties.class),
context.getBean(OtelResourceProperties.class),
context.getBean(PropagationProperties.class),
context.getBean(OtelSpringProperties.class),
DefaultConfigProperties.createFromMap(fallback));
assertThat(config.getMap("otel.resource.attributes"))

View File

@ -9,8 +9,8 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.asser
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.PropagationProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
@ -68,7 +68,7 @@ public class SpringResourceProviderTest {
context.getBean(Environment.class),
new OtlpExporterProperties(),
new OtelResourceProperties(),
new PropagationProperties(),
new OtelSpringProperties(),
DefaultConfigProperties.createFromMap(Collections.emptyMap()));
return assertThat(

View File

@ -48,8 +48,7 @@ public class ResourceProviderPropertiesCustomizer implements AutoConfigurationCu
autoConfigurationCustomizer.addPropertiesCustomizer(this::customize);
}
// VisibleForTesting
Map<String, String> customize(ConfigProperties config) {
public Map<String, String> customize(ConfigProperties config) {
Set<String> enabledProviders = new HashSet<>(config.getList(ENABLED_KEY));
List<String> enabled = new ArrayList<>();

View File

@ -13,8 +13,8 @@ import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelResourceProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtelSpringProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.OtlpExporterProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.PropagationProperties;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.SpringConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@ -59,7 +59,7 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
@Autowired private TestRestTemplate testRestTemplate;
@Autowired private Environment environment;
@Autowired private PropagationProperties propagationProperties;
@Autowired private OtelSpringProperties otelSpringProperties;
@Autowired private OtelResourceProperties otelResourceProperties;
@Autowired private OtlpExporterProperties otlpExporterProperties;
@Autowired private RestTemplateBuilder restTemplateBuilder;
@ -128,7 +128,7 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
environment,
otlpExporterProperties,
otelResourceProperties,
propagationProperties,
otelSpringProperties,
DefaultConfigProperties.createFromMap(
Collections.singletonMap("otel.exporter.otlp.headers", "a=1,b=2")));
assertThat(configProperties.getMap("otel.exporter.otlp.headers"))