diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt index d3e20fa534c7..f7d08e73eeca 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-spring-boot-autoconfigure.txt @@ -1,2 +1,9 @@ Comparing source compatibility of opentelemetry-spring-boot-autoconfigure-2.22.0-SNAPSHOT.jar against opentelemetry-spring-boot-autoconfigure-2.21.0.jar -No changes. \ No newline at end of file +=== UNCHANGED CLASS: PUBLIC io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + --- REMOVED ANNOTATION: org.springframework.boot.context.properties.EnableConfigurationProperties + --- REMOVED 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 (-) ++++ NEW CLASS: PUBLIC(+) io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationAccess (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) STATIC(+) com.fasterxml.jackson.databind.ObjectMapper getObjectMapper() diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 1989589aabc3..0d857ffb131c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -65,7 +65,9 @@ dependencies { library("org.springframework.boot:spring-boot-starter-data-jdbc:$springBootVersion") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + implementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") implementation(project(":sdk-autoconfigure-support")) + implementation(project(":declarative-config-bridge")) compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators") compileOnly("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator") compileOnly("io.opentelemetry:opentelemetry-exporter-logging") @@ -80,6 +82,7 @@ dependencies { testLibrary("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { exclude("org.junit.vintage", "junit-vintage-engine") } + testImplementation("javax.servlet:javax.servlet-api:3.1.0") testImplementation("jakarta.servlet:jakarta.servlet-api:5.0.0") testRuntimeOnly("com.h2database:h2:1.4.197") @@ -171,6 +174,17 @@ testing { } } } + + val testDeclarativeConfig by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation("io.opentelemetry:opentelemetry-sdk") + implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { + exclude("org.junit.vintage", "junit-vintage-engine") + } + implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion") + } + } } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java new file mode 100644 index 000000000000..ea30bccea642 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFile.java @@ -0,0 +1,128 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationAccess; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; + +class EmbeddedConfigFile { + + private static final Pattern ARRAY_PATTERN = Pattern.compile("(.+)\\[(\\d+)]$"); + + private EmbeddedConfigFile() {} + + static OpenTelemetryConfigurationModel extractModel(ConfigurableEnvironment environment) { + Map props = extractSpringProperties(environment); + return convertToOpenTelemetryConfigurationModel(props); + } + + private static Map extractSpringProperties(ConfigurableEnvironment environment) { + MutablePropertySources propertySources = environment.getPropertySources(); + + Map props = new HashMap<>(); + for (PropertySource propertySource : propertySources) { + if (propertySource instanceof EnumerablePropertySource) { + for (String propertyName : + ((EnumerablePropertySource) propertySource).getPropertyNames()) { + if (propertyName.startsWith("otel.")) { + String property = environment.getProperty(propertyName); + if (Objects.equals(property, "")) { + property = null; // spring returns empty string for yaml null + } + props.put(propertyName.substring("otel.".length()), property); + } + } + } + } + + if (props.isEmpty()) { + throw new IllegalStateException( + "No properties found with prefix 'otel.' - this should not happen, because we checked " + + "'environment.getProperty(\"otel.file_format\", String.class) != null' earlier"); + } + return props; + } + + static OpenTelemetryConfigurationModel convertToOpenTelemetryConfigurationModel( + Map flatProps) { + Map nested = convertFlatPropsToNested(flatProps); + + return DeclarativeConfigurationAccess.getObjectMapper() + .convertValue(nested, OpenTelemetryConfigurationModel.class); + } + + /** + * Convert flat property map to nested structure. e.g. "otel.instrumentation.java.list[0]" = "one" + * and "otel.instrumentation.java.list[1]" = "two" becomes: {otel: {instrumentation: {java: {list: + * ["one", "two"]}}}} + */ + @SuppressWarnings("unchecked") + static Map convertFlatPropsToNested(Map flatProps) { + Map result = new HashMap<>(); + + for (Map.Entry entry : flatProps.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + // Split the key by dots + String[] parts = key.split("\\."); + Map current = result; + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + boolean isLast = (i == parts.length - 1); + + // Check if this part contains an array index like "list[0]" + Matcher matcher = ARRAY_PATTERN.matcher(part); + if (matcher.matches()) { + String arrayName = matcher.group(1); + int index = Integer.parseInt(matcher.group(2)); + + ArrayList list = + (ArrayList) current.computeIfAbsent(arrayName, k -> new ArrayList<>()); + + // Ensure the list is large enough + list.ensureCapacity(index + 1); + while (list.size() <= index) { + list.add(null); + } + + if (isLast) { + list.set(index, value); + } else { + // Need to create a nested map at this index + if (list.get(index) == null) { + list.set(index, new HashMap()); + } + current = (Map) list.get(index); + } + } else { + // Regular property (not an array) + if (isLast) { + current.put(part, value); + } else { + // Need to create a nested map + if (!current.containsKey(part)) { + current.put(part, new HashMap()); + } + current = (Map) current.get(part); + } + } + } + } + return result; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index b9a9bd7c9a2a..9728579c03c0 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -6,23 +6,38 @@ package io.opentelemetry.instrumentation.spring.autoconfigure; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties; +import io.opentelemetry.instrumentation.config.bridge.DeclarativeConfigPropertiesBridgeBuilder; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.DeclarativeConfigDisabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.DeclarativeConfigEnabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelDisabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelEnabled; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelMapConverter; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; 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.SpringConfigProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.DistroComponentProvider; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.DistroVersionResourceProvider; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.ResourceCustomizerProvider; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources.SpringResourceProvider; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -33,7 +48,6 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.info.BuildProperties; @@ -41,7 +55,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; /** @@ -52,100 +66,157 @@ *

Updates the sampler probability for the configured {@link TracerProvider}. */ @Configuration -@EnableConfigurationProperties({ - OtlpExporterProperties.class, - OtelResourceProperties.class, - OtelSpringProperties.class -}) public class OpenTelemetryAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(OpenTelemetryAutoConfiguration.class); public OpenTelemetryAutoConfiguration() {} - @Bean - @ConfigurationPropertiesBinding - OtelMapConverter otelMapConverter() { - // This is needed for otlp exporter headers and OtelResourceProperties. - - // We need this converter, even if the SDK is disabled, - // because the properties are parsed before the SDK is disabled. - - // We also need this converter if the OpenTelemetry bean is user supplied, - // because the environment variables may still contain a value that needs to be converted, - // even if the SDK is disabled (and the value thus ignored). - return new OtelMapConverter(); - } - @Configuration - @Conditional(SdkEnabled.class) - @DependsOn("otelMapConverter") + @Conditional(OtelEnabled.class) @ConditionalOnMissingBean(OpenTelemetry.class) static class OpenTelemetrySdkConfig { - @Bean - public OpenTelemetrySdkComponentLoader openTelemetrySdkComponentLoader( - ApplicationContext applicationContext) { - return new OpenTelemetrySdkComponentLoader(applicationContext); - } + public OpenTelemetrySdkConfig() {} - @Bean - public ResourceProvider otelSpringResourceProvider(Optional buildProperties) { - return new SpringResourceProvider(buildProperties); - } + @Configuration + @EnableConfigurationProperties({ + OtlpExporterProperties.class, + OtelResourceProperties.class, + OtelSpringProperties.class + }) + @Conditional(DeclarativeConfigDisabled.class) + static class PropertiesConfig { - @Bean - public ResourceProvider otelDistroVersionResourceProvider() { - return new DistroVersionResourceProvider(); - } + @Bean + public ResourceProvider otelSpringResourceProvider( + Optional buildProperties) { + return new SpringResourceProvider(buildProperties); + } - @Bean - public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( - Environment env, - OtlpExporterProperties otlpExporterProperties, - OtelResourceProperties resourceProperties, - OtelSpringProperties otelSpringProperties, - OpenTelemetrySdkComponentLoader componentLoader) { - - return AutoConfigureUtil.setConfigPropertiesCustomizer( - AutoConfiguredOpenTelemetrySdk.builder().setComponentLoader(componentLoader), - c -> - SpringConfigProperties.create( - env, otlpExporterProperties, resourceProperties, otelSpringProperties, c)) - .build(); - } + @Bean + public ResourceProvider otelDistroVersionResourceProvider() { + return new DistroVersionResourceProvider(); + } - @Bean - public OpenTelemetry openTelemetry( - AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { - logger.info( - "OpenTelemetry Spring Boot starter ({}) has been started", - EmbeddedInstrumentationProperties.findVersion( - "io.opentelemetry.spring-boot-autoconfigure")); - - return autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk(); + @Bean + @ConfigurationPropertiesBinding + OtelMapConverter otelMapConverter() { + return new OtelMapConverter(); + } + + @Bean + public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( + Environment env, + OtlpExporterProperties otlpExporterProperties, + OtelResourceProperties resourceProperties, + OtelSpringProperties otelSpringProperties, + ApplicationContext applicationContext) { + + return AutoConfigureUtil.setConfigPropertiesCustomizer( + AutoConfiguredOpenTelemetrySdk.builder() + .setComponentLoader(new OpenTelemetrySdkComponentLoader(applicationContext)), + c -> + SpringConfigProperties.create( + env, otlpExporterProperties, resourceProperties, otelSpringProperties, c)) + .build(); + } + + @Bean + public OpenTelemetry openTelemetry( + AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + logStart(); + return autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk(); + } + + @Bean + public InstrumentationConfig instrumentationConfig(ConfigProperties properties) { + return new ConfigPropertiesBridge(properties); + } + + /** + * Expose the {@link ConfigProperties} bean for use in other auto-configurations. + * + *

Not using spring boot properties directly in order to support {@link + * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesCustomizer(Function)} + * and {@link + * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesSupplier(Supplier)}. + */ + @Bean + public ConfigProperties otelProperties( + AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + return AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk); + } } - /** - * Expose the {@link ConfigProperties} bean for use in other auto-configurations. - * - *

Not using spring boot properties directly in order to support {@link - * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesCustomizer(Function)} - * and {@link - * io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer#addPropertiesSupplier(Supplier)}. - */ - @Bean - public ConfigProperties otelProperties( - AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { - return AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk); + @Configuration + @Conditional(DeclarativeConfigEnabled.class) + static class EmbeddedConfigFileConfig { + + @Bean + public OpenTelemetryConfigurationModel openTelemetryConfigurationModel( + ConfigurableEnvironment environment) throws IOException { + return EmbeddedConfigFile.extractModel(environment); + } + + @Bean + public OpenTelemetry openTelemetry( + OpenTelemetryConfigurationModel model, ApplicationContext applicationContext) { + OpenTelemetrySdk sdk = + DeclarativeConfiguration.create( + model, new OpenTelemetrySdkComponentLoader(applicationContext)); + Runtime.getRuntime().addShutdownHook(new Thread(sdk::close)); + logStart(); + return sdk; + } + + @Bean + public ConfigProvider configProvider(OpenTelemetryConfigurationModel model) { + return SpringConfigProvider.create(model); + } + + /** + * Expose the {@link ConfigProperties} bean for use in other auto-configurations. + * + *

Not using spring boot properties directly, because declarative configuration does not + * integrate with spring boot properties. + */ + @Bean + public ConfigProperties otelProperties(ConfigProvider configProvider) { + return new DeclarativeConfigPropertiesBridgeBuilder() + .buildFromInstrumentationConfig(configProvider.getInstrumentationConfig()); + } + + @Bean + public InstrumentationConfig instrumentationConfig( + ConfigProperties properties, ConfigProvider configProvider) { + return new ConfigPropertiesBridge(properties, configProvider); + } + + @Bean + public DeclarativeConfigurationCustomizerProvider distroConfigurationCustomizerProvider() { + return new ResourceCustomizerProvider(); + } + + @Bean + public ComponentProvider distroComponentProvider() { + return new DistroComponentProvider(); + } } } + private static void logStart() { + logger.info( + "OpenTelemetry Spring Boot starter ({}) has been started", + EmbeddedInstrumentationProperties.findVersion( + "io.opentelemetry.spring-boot-autoconfigure")); + } + @Configuration - @DependsOn("otelMapConverter") @ConditionalOnMissingBean(OpenTelemetry.class) - @ConditionalOnProperty(name = "otel.sdk.disabled", havingValue = "true") + @Conditional(OtelDisabled.class) static class DisabledOpenTelemetrySdkConfig { + @Bean public OpenTelemetry openTelemetry() { logger.info("OpenTelemetry Spring Boot starter has been disabled"); @@ -157,6 +228,26 @@ public OpenTelemetry openTelemetry() { public ConfigProperties otelProperties() { return DefaultConfigProperties.createFromMap(Collections.emptyMap()); } + + @Bean + public InstrumentationConfig instrumentationConfig(ConfigProperties properties) { + return new ConfigPropertiesBridge(properties, null); + } + + @Configuration + @Conditional(DeclarativeConfigDisabled.class) + static class PropertiesConfig { + /** + * Is only added so that we have the same converters as with active OpenTelemetry SDK + * + *

In other words, don't break applications that (accidentally) use the {@link + * OtelMapConverter}. + */ + @Bean + OtelMapConverter otelMapConverter() { + return new OtelMapConverter(); + } + } } @Configuration @@ -170,6 +261,16 @@ public ConfigProperties otelProperties(ApplicationContext applicationContext) { } } + @Configuration + @ConditionalOnBean(OpenTelemetry.class) + @ConditionalOnMissingBean({InstrumentationConfig.class}) + static class FallbackInstrumentationConfig { + @Bean + public InstrumentationConfig instrumentationConfig(ConfigProperties properties) { + return new ConfigPropertiesBridge(properties, null); + } + } + /** * The {@link ComponentLoader} is used by the SDK autoconfiguration to load all components, e.g. * configurationMap = + DeclarativeConfigurationAccess.getObjectMapper() + .convertValue(model, new TypeReference>() {}); + if (configurationMap == null) { + configurationMap = Collections.emptyMap(); + } + return SpringDeclarativeConfigProperties.create(configurationMap, componentLoader); + } + + /** + * Create a {@link SpringConfigProvider} from the {@code model}. + * + * @param model the configuration model + * @return the {@link SpringConfigProvider} + */ + static SpringConfigProvider create(OpenTelemetryConfigurationModel model) { + return new SpringConfigProvider( + model, ComponentLoader.forClassLoader(SpringConfigProvider.class.getClassLoader())); + } + + @Nullable + @Override + public DeclarativeConfigProperties getInstrumentationConfig() { + return instrumentationConfig; + } + + @Override + public String toString() { + return "SpringConfigProvider{" + "instrumentationConfig=" + instrumentationConfig + '}'; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java new file mode 100644 index 000000000000..a5f7bfbf59f8 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/SpringDeclarativeConfigProperties.java @@ -0,0 +1,316 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigException; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import javax.annotation.Nullable; + +/** + * Spring flavor of {@link + * io.opentelemetry.sdk.extension.incubator.fileconfig.YamlDeclarativeConfigProperties}, that tries + * to coerce types, because spring doesn't tell what the original type was. + */ +final class SpringDeclarativeConfigProperties implements DeclarativeConfigProperties { + + private static final Set> SUPPORTED_SCALAR_TYPES = + Collections.unmodifiableSet( + new LinkedHashSet<>( + Arrays.asList(String.class, Boolean.class, Long.class, Double.class))); + + /** Values are {@link #isPrimitive(Object)}, {@link List} of scalars. */ + private final Map simpleEntries; + + private final Map> listEntries; + private final Map mapEntries; + private final ComponentLoader componentLoader; + + private SpringDeclarativeConfigProperties( + Map simpleEntries, + Map> listEntries, + Map mapEntries, + ComponentLoader componentLoader) { + this.simpleEntries = simpleEntries; + this.listEntries = listEntries; + this.mapEntries = mapEntries; + this.componentLoader = componentLoader; + } + + /** + * Create a {@link SpringDeclarativeConfigProperties} from the {@code properties} map. + * + *

{@code properties} is expected to be the output of YAML parsing (i.e. with Jackson {@code + * com.fasterxml.jackson.databind.ObjectMapper}), and have values which are scalars, lists of + * scalars, lists of maps, and maps. + * + * @see DeclarativeConfiguration#toConfigProperties(Object) + */ + @SuppressWarnings("unchecked") + public static SpringDeclarativeConfigProperties create( + Map properties, ComponentLoader componentLoader) { + Map simpleEntries = new LinkedHashMap<>(); + Map> listEntries = new LinkedHashMap<>(); + Map mapEntries = new LinkedHashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (isPrimitive(value) || value == null) { + simpleEntries.put(key, value); + continue; + } + if (isPrimitiveList(value)) { + simpleEntries.put(key, value); + continue; + } + if (isListOfMaps(value)) { + List list = + ((List>) value) + .stream() + .map(map -> SpringDeclarativeConfigProperties.create(map, componentLoader)) + .collect(toList()); + listEntries.put(key, list); + continue; + } + if (isMap(value)) { + SpringDeclarativeConfigProperties configProperties = + SpringDeclarativeConfigProperties.create((Map) value, componentLoader); + mapEntries.put(key, configProperties); + continue; + } + throw new DeclarativeConfigException( + "Unable to initialize ExtendedConfigProperties. Key \"" + + key + + "\" has unrecognized object type " + + value.getClass().getName()); + } + return new SpringDeclarativeConfigProperties( + simpleEntries, listEntries, mapEntries, componentLoader); + } + + private static boolean isPrimitiveList(Object object) { + if (object instanceof List) { + List list = (List) object; + return list.stream().allMatch(SpringDeclarativeConfigProperties::isPrimitive); + } + return false; + } + + private static boolean isPrimitive(Object object) { + return object instanceof String + || object instanceof Integer + || object instanceof Long + || object instanceof Float + || object instanceof Double + || object instanceof Boolean; + } + + private static boolean isListOfMaps(Object object) { + if (object instanceof List) { + List list = (List) object; + return list.stream() + .allMatch( + entry -> + entry instanceof Map + && ((Map) entry) + .keySet().stream().allMatch(key -> key instanceof String)); + } + return false; + } + + private static boolean isMap(Object object) { + if (object instanceof Map) { + Map map = (Map) object; + return map.keySet().stream().allMatch(entry -> entry instanceof String); + } + return false; + } + + @Nullable + @Override + public String getString(String name) { + return stringOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Boolean getBoolean(String name) { + return booleanOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Integer getInt(String name) { + Object value = simpleEntries.get(name); + if (value == null) { + return null; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Long) { + return ((Long) value).intValue(); + } + return Integer.parseInt(value.toString()); + } + + @Nullable + @Override + public Long getLong(String name) { + return longOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + public Double getDouble(String name) { + return doubleOrNull(simpleEntries.get(name)); + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public List getScalarList(String name, Class scalarType) { + if (!SUPPORTED_SCALAR_TYPES.contains(scalarType)) { + throw new DeclarativeConfigException( + "Unsupported scalar type " + + scalarType.getName() + + ". Supported types include " + + SUPPORTED_SCALAR_TYPES.stream() + .map(Class::getName) + .collect(joining(",", "[", "]"))); + } + Object value = simpleEntries.get(name); + if (value instanceof List) { + List objectList = ((List) value); + if (objectList.isEmpty()) { + return Collections.emptyList(); + } + List result = + (List) + objectList.stream() + .map( + entry -> { + if (scalarType == String.class) { + return stringOrNull(entry); + } else if (scalarType == Boolean.class) { + return booleanOrNull(entry); + } else if (scalarType == Long.class) { + return longOrNull(entry); + } else if (scalarType == Double.class) { + return doubleOrNull(entry); + } + return null; + }) + .filter(Objects::nonNull) + .collect(toList()); + if (result.isEmpty()) { + return null; + } + return result; + } + return null; + } + + @Nullable + private static String stringOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + return value.toString(); + } + + @Nullable + private static Boolean booleanOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + return Boolean.parseBoolean(value.toString()); + } + + @Nullable + private static Long longOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Integer) { + return ((Integer) value).longValue(); + } + if (value instanceof Long) { + return (Long) value; + } + return Long.parseLong(value.toString()); + } + + @Nullable + private static Double doubleOrNull(@Nullable Object value) { + if (value == null) { + return null; + } + if (value instanceof Float) { + return ((Float) value).doubleValue(); + } + if (value instanceof Double) { + return (Double) value; + } + return Double.parseDouble(value.toString()); + } + + @Nullable + @Override + public DeclarativeConfigProperties getStructured(String name) { + return mapEntries.get(name); + } + + @Nullable + @Override + public List getStructuredList(String name) { + List value = listEntries.get(name); + if (value != null) { + return Collections.unmodifiableList(value); + } + return null; + } + + @Override + public Set getPropertyKeys() { + Set keys = new LinkedHashSet<>(); + keys.addAll(simpleEntries.keySet()); + keys.addAll(listEntries.keySet()); + keys.addAll(mapEntries.keySet()); + return Collections.unmodifiableSet(keys); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", ", "SpringDeclarativeConfigProperties{", "}"); + simpleEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + listEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + mapEntries.forEach((key, value) -> joiner.add(key + "=" + value)); + return joiner.toString(); + } + + /** Return the {@link ComponentLoader}. */ + @Override + public ComponentLoader getComponentLoader() { + return componentLoader; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java index 7c55f430d1e4..0383c5117298 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/ConditionalOnEnabledInstrumentation.java @@ -20,7 +20,7 @@ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @ConditionalOnBean(OpenTelemetry.class) -@Conditional({SdkEnabled.class, InstrumentationPropertyEnabled.class}) +@Conditional({OtelEnabled.class, InstrumentationPropertyEnabled.class}) public @interface ConditionalOnEnabledInstrumentation { String module(); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigDisabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigDisabled.java new file mode 100644 index 000000000000..72ceddb62cd5 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigDisabled.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class DeclarativeConfigDisabled implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !EarlyConfig.isDeclarativeConfig(context.getEnvironment()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigEnabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigEnabled.java new file mode 100644 index 000000000000..b64134d72f93 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/DeclarativeConfigEnabled.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class DeclarativeConfigEnabled implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return EarlyConfig.isDeclarativeConfig(context.getEnvironment()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/EarlyConfig.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/EarlyConfig.java new file mode 100644 index 000000000000..19a64749a5b9 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/EarlyConfig.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import org.springframework.core.env.Environment; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class EarlyConfig { + private EarlyConfig() {} + + public static boolean otelEnabled(Environment environment) { + boolean disabled = + environment.getProperty( + getPropertyName(environment, "otel.sdk.disabled", "otel.disabled"), + Boolean.class, + false); + return !disabled; + } + + public static boolean isDeclarativeConfig(Environment environment) { + return environment.getProperty("otel.file_format", String.class) != null; + } + + public static boolean isDefaultEnabled(Environment environment) { + if (isDeclarativeConfig(environment)) { + String mode = + environment.getProperty( + "otel.instrumentation/development.java.spring_starter.instrumentation_mode", + String.class, + "default"); + + switch (mode) { + case "none": + return false; + case "default": + return true; + default: + throw new ConfigurationException("Unknown instrumentation mode: " + mode); + } + } else { + return environment.getProperty( + "otel.instrumentation.common.default-enabled", Boolean.class, true); + } + } + + public static String translatePropertyName(Environment environment, String name) { + if (isDeclarativeConfig(environment)) { + if (name.startsWith("otel.instrumentation.")) { + return toSnakeCase( + String.format( + "otel.instrumentation/development.java.%s", + name.substring("otel.instrumentation.".length()))); + } + + throw new IllegalStateException( + "No mapping found for property name: " + name + ". Please report this bug."); + } else { + return name; + } + } + + private static String toSnakeCase(String string) { + return string.replace('-', '_'); + } + + public static boolean isInstrumentationEnabled( + Environment environment, String name, boolean defaultValue) { + String property = + getPropertyName( + environment, + String.format("otel.instrumentation.%s.enabled", name), + String.format("otel.instrumentation/development.java.%s.enabled", toSnakeCase(name))); + Boolean explicit = environment.getProperty(property, Boolean.class); + if (explicit != null) { + return explicit; + } + if (!defaultValue) { + return false; + } + return isDefaultEnabled(environment); + } + + private static String getPropertyName( + Environment environment, String propertyBased, String declarative) { + return isDeclarativeConfig(environment) ? declarative : propertyBased; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java index 87687f668b6c..39de2fc9227b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/InstrumentationPropertyEnabled.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal; import java.util.Map; +import java.util.Objects; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -19,19 +20,12 @@ public class InstrumentationPropertyEnabled implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Map attributes = - metadata.getAnnotationAttributes(ConditionalOnEnabledInstrumentation.class.getName()); + Objects.requireNonNull( + metadata.getAnnotationAttributes(ConditionalOnEnabledInstrumentation.class.getName())); - String name = String.format("otel.instrumentation.%s.enabled", attributes.get("module")); - Boolean explicit = context.getEnvironment().getProperty(name, Boolean.class); - if (explicit != null) { - return explicit; - } - boolean defaultValue = (boolean) attributes.get("enabledByDefault"); - if (!defaultValue) { - return false; - } - return context - .getEnvironment() - .getProperty("otel.instrumentation.common.default-enabled", Boolean.class, true); + return EarlyConfig.isInstrumentationEnabled( + context.getEnvironment(), + attributes.get("module").toString(), + (boolean) attributes.get("enabledByDefault")); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelDisabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelDisabled.java new file mode 100644 index 000000000000..d521f76eb7ba --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelDisabled.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class OtelDisabled implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !EarlyConfig.otelEnabled(context.getEnvironment()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelEnabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelEnabled.java new file mode 100644 index 000000000000..a1b87384e8ce --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/OtelEnabled.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class OtelEnabled implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return EarlyConfig.otelEnabled(context.getEnvironment()); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java deleted file mode 100644 index 082bdebc00b7..000000000000 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/SdkEnabled.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.spring.autoconfigure.internal; - -import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -public class SdkEnabled extends AnyNestedCondition { - public SdkEnabled() { - super(ConfigurationPhase.PARSE_CONFIGURATION); - } - - @ConditionalOnProperty(name = "otel.sdk.disabled", havingValue = "false", matchIfMissing = true) - static class NotDisabled {} -} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java index cd8d7ca3d38b..3486c5fffe8b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/DataSourcePostProcessor.java @@ -7,11 +7,11 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetryBuilder; import io.opentelemetry.instrumentation.jdbc.datasource.internal.Experimental; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; @@ -30,13 +30,13 @@ final class DataSourcePostProcessor implements BeanPostProcessor, Ordered { private static final Class ROUTING_DATA_SOURCE_CLASS = getRoutingDataSourceClass(); private final ObjectProvider openTelemetryProvider; - private final ObjectProvider configPropertiesProvider; + private final ObjectProvider configProvider; DataSourcePostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { this.openTelemetryProvider = openTelemetryProvider; - this.configPropertiesProvider = configPropertiesProvider; + this.configProvider = configProvider; } private static Class getRoutingDataSourceClass() { @@ -59,32 +59,24 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { && !isRoutingDatasource(bean) && !ScopedProxyUtils.isScopedTarget(beanName)) { DataSource dataSource = (DataSource) bean; + InstrumentationConfig config = configProvider.getObject(); JdbcTelemetryBuilder builder = JdbcTelemetry.builder(openTelemetryProvider.getObject()) .setStatementSanitizationEnabled( InstrumentationConfigUtil.isStatementSanitizationEnabled( - configPropertiesProvider.getObject(), - "otel.instrumentation.jdbc.statement-sanitizer.enabled")) + config, "otel.instrumentation.jdbc.statement-sanitizer.enabled")) .setCaptureQueryParameters( - configPropertiesProvider - .getObject() - .getBoolean( - "otel.instrumentation.jdbc.experimental.capture-query-parameters", false)) + config.getBoolean( + "otel.instrumentation.jdbc.experimental.capture-query-parameters", false)) .setTransactionInstrumenterEnabled( - configPropertiesProvider - .getObject() - .getBoolean( - "otel.instrumentation.jdbc.experimental.transaction.enabled", false)) + config.getBoolean( + "otel.instrumentation.jdbc.experimental.transaction.enabled", false)) .setDataSourceInstrumenterEnabled( - configPropertiesProvider - .getObject() - .getBoolean( - "otel.instrumentation.jdbc.experimental.datasource.enabled", false)); + config.getBoolean( + "otel.instrumentation.jdbc.experimental.datasource.enabled", false)); Experimental.setEnableSqlCommenter( builder, - configPropertiesProvider - .getObject() - .getBoolean("otel.instrumentation.jdbc.experimental.sqlcommenter.enabled", false)); + config.getBoolean("otel.instrumentation.jdbc.experimental.sqlcommenter.enabled", false)); DataSource otelDataSource = builder.build().wrap(dataSource); // wrap instrumented data source into a proxy that unwraps to the original data source diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java index 3ec55a5a06c2..1732c3be178d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.jdbc; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import javax.sql.DataSource; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -33,7 +33,7 @@ public JdbcInstrumentationAutoConfiguration() {} // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning static DataSourcePostProcessor dataSourcePostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { - return new DataSourcePostProcessor(openTelemetryProvider, configPropertiesProvider); + ObjectProvider configProvider) { + return new DataSourcePostProcessor(openTelemetryProvider, configProvider); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java index 44f845e25ff1..4df611685733 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/kafka/KafkaInstrumentationAutoConfiguration.java @@ -6,10 +6,10 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.kafka; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.kafka.v2_7.SpringKafkaTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -42,10 +42,10 @@ DefaultKafkaProducerFactoryCustomizer otelKafkaProducerFactoryCustomizer( @Bean static SpringKafkaTelemetry getTelemetry( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { return SpringKafkaTelemetry.builder(openTelemetryProvider.getObject()) .setCaptureExperimentalSpanAttributes( - configPropertiesProvider + configProvider .getObject() .getBoolean("otel.instrumentation.kafka.experimental-span-attributes", false)) .build(); @@ -60,8 +60,8 @@ static SpringKafkaTelemetry getTelemetry( static ConcurrentKafkaListenerContainerFactoryPostProcessor otelKafkaListenerContainerFactoryBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { return new ConcurrentKafkaListenerContainerFactoryPostProcessor( - () -> getTelemetry(openTelemetryProvider, configPropertiesProvider)); + () -> getTelemetry(openTelemetryProvider, configProvider)); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java index f6354622174c..55828481fb3c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LogbackAppenderInstaller.java @@ -10,12 +10,14 @@ import ch.qos.logback.core.Appender; import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.DeprecatedConfigProperties; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.EarlyConfig; import java.util.Iterator; import java.util.Optional; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.core.env.ConfigurableEnvironment; class LogbackAppenderInstaller { @@ -41,25 +43,18 @@ static void install(ApplicationEnvironmentPreparedEvent applicationEnvironmentPr } } - private static boolean isLogbackAppenderAddable( - ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { - return isAppenderAddable( - applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-appender.enabled"); + private static boolean isLogbackAppenderAddable(ApplicationEnvironmentPreparedEvent event) { + return isAppenderAddable(event, "logback-appender"); } - private static boolean isLogbackMdcAppenderAddable( - ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) { - return isAppenderAddable( - applicationEnvironmentPreparedEvent, "otel.instrumentation.logback-mdc.enabled"); + private static boolean isLogbackMdcAppenderAddable(ApplicationEnvironmentPreparedEvent event) { + return isAppenderAddable(event, "logback-mdc"); } - private static boolean isAppenderAddable( - ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) { - boolean otelSdkDisabled = - evaluateBooleanProperty(applicationEnvironmentPreparedEvent, "otel.sdk.disabled", false); - boolean logbackInstrumentationEnabled = - evaluateBooleanProperty(applicationEnvironmentPreparedEvent, property, true); - return !otelSdkDisabled && logbackInstrumentationEnabled; + private static boolean isAppenderAddable(ApplicationEnvironmentPreparedEvent event, String name) { + ConfigurableEnvironment environment = event.getEnvironment(); + return EarlyConfig.otelEnabled(environment) + && EarlyConfig.isInstrumentationEnabled(environment, name, true); } private static void reInitializeOpenTelemetryAppender( @@ -236,18 +231,9 @@ private static void initializeMdcAppenderFromProperties( private static Boolean evaluateBooleanProperty( ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, String property) { - return applicationEnvironmentPreparedEvent - .getEnvironment() - .getProperty(property, Boolean.class); - } - - private static boolean evaluateBooleanProperty( - ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent, - String property, - boolean defaultValue) { - return applicationEnvironmentPreparedEvent - .getEnvironment() - .getProperty(property, Boolean.class, defaultValue); + ConfigurableEnvironment environment = applicationEnvironmentPreparedEvent.getEnvironment(); + return environment.getProperty( + EarlyConfig.translatePropertyName(environment, property), Boolean.class); } private static Optional findAppender(Class appenderClass) { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfiguration.java index 0638941a18af..a72a9db38d6f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfiguration.java @@ -8,7 +8,7 @@ import static java.util.Collections.emptyList; import io.opentelemetry.exporter.logging.LoggingSpanExporter; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.OtelEnabled; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; @@ -23,7 +23,7 @@ * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -@Conditional(SdkEnabled.class) +@Conditional(OtelEnabled.class) // to match "otel.javaagent.debug" system property @ConditionalOnProperty(name = "otel.spring-starter.debug", havingValue = "true") @ConditionalOnClass(LoggingSpanExporter.class) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java index 7fae726c9fb8..a640bde9aebb 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/OpenTelemetryAppenderAutoConfiguration.java @@ -18,9 +18,10 @@ * any time. */ @Configuration -@SuppressWarnings("OtelPrivateConstructorForUtilityClass") public class OpenTelemetryAppenderAutoConfiguration { + public OpenTelemetryAppenderAutoConfiguration() {} + @ConditionalOnEnabledInstrumentation(module = "log4j-appender") @ConditionalOnClass(org.apache.logging.log4j.core.LoggerContext.class) @Configuration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java index 3685b6db2cab..fcc3bc46a315 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/mongo/MongoClientInstrumentationAutoConfiguration.java @@ -7,10 +7,10 @@ import com.mongodb.MongoClientSettings; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer; import org.springframework.context.annotation.Bean; @@ -27,7 +27,7 @@ public class MongoClientInstrumentationAutoConfiguration { @Bean MongoClientSettingsBuilderCustomizer customizer( - OpenTelemetry openTelemetry, ConfigProperties config) { + OpenTelemetry openTelemetry, InstrumentationConfig config) { return builder -> builder.addCommandListener( MongoTelemetry.builder(openTelemetry) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java index e1bc89ace3d0..4dcddb592aca 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.r2dbc.spi.ConnectionFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -29,7 +29,7 @@ public R2dbcInstrumentationAutoConfiguration() {} // static to avoid "is not eligible for getting processed by all BeanPostProcessors" warning static R2dbcInstrumentingPostProcessor r2dbcInstrumentingPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { - return new R2dbcInstrumentingPostProcessor(openTelemetryProvider, configPropertiesProvider); + ObjectProvider configProvider) { + return new R2dbcInstrumentingPostProcessor(openTelemetryProvider, configProvider); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java index 5d75b28946ef..09849deef13b 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2dbcInstrumentingPostProcessor.java @@ -6,9 +6,9 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.r2dbc; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.r2dbc.v1_0.internal.shaded.R2dbcTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import org.springframework.aop.scope.ScopedProxyUtils; @@ -19,13 +19,13 @@ class R2dbcInstrumentingPostProcessor implements BeanPostProcessor { private final ObjectProvider openTelemetryProvider; - private final ObjectProvider configPropertiesProvider; + private final ObjectProvider configProvider; R2dbcInstrumentingPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { this.openTelemetryProvider = openTelemetryProvider; - this.configPropertiesProvider = configPropertiesProvider; + this.configProvider = configProvider; } @Override @@ -35,7 +35,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { return R2dbcTelemetry.builder(openTelemetryProvider.getObject()) .setStatementSanitizationEnabled( InstrumentationConfigUtil.isStatementSanitizationEnabled( - configPropertiesProvider.getObject(), + configProvider.getObject(), "otel.instrumentation.r2dbc.statement-sanitizer.enabled")) .build() .wrapConnectionFactory(connectionFactory, getConnectionFactoryOptions(connectionFactory)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java index f58f4da5f217..e88d02ac0c62 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/runtimemetrics/RuntimeMetricsAutoConfiguration.java @@ -6,9 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.runtimemetrics; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.Comparator; import java.util.Optional; import javax.annotation.PreDestroy; @@ -45,8 +44,7 @@ public void stopMetrics() throws Exception { public void handleApplicationReadyEvent(ApplicationReadyEvent event) { ConfigurableApplicationContext applicationContext = event.getApplicationContext(); OpenTelemetry openTelemetry = applicationContext.getBean(OpenTelemetry.class); - ConfigPropertiesBridge config = - new ConfigPropertiesBridge(applicationContext.getBean(ConfigProperties.class)); + InstrumentationConfig config = applicationContext.getBean(InstrumentationConfig.class); double version = Math.max(8, Double.parseDouble(System.getProperty("java.specification.version"))); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java index 5dfec2280e8e..6d84cde77985 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAspect.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter; import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor; @@ -16,7 +17,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -43,7 +43,7 @@ final class SpringSchedulingInstrumentationAspect { private final Instrumenter instrumenter; public SpringSchedulingInstrumentationAspect( - OpenTelemetry openTelemetry, ConfigProperties configProperties) { + OpenTelemetry openTelemetry, InstrumentationConfig config) { CodeAttributesGetter codedAttributesGetter = ClassAndMethod.codeAttributesGetter(); InstrumenterBuilder builder = @@ -52,7 +52,7 @@ public SpringSchedulingInstrumentationAspect( INSTRUMENTATION_NAME, CodeSpanNameExtractor.create(codedAttributesGetter)) .addAttributesExtractor(CodeAttributesExtractor.create(codedAttributesGetter)); - if (configProperties.getBoolean( + if (config.getBoolean( "otel.instrumentation.spring-scheduling.experimental-span-attributes", false)) { builder.addAttributesExtractor( AttributesExtractor.constant(AttributeKey.stringKey("job.system"), "spring_scheduling")); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java index c65ea90ac4b1..85a54625e455 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SpringSchedulingInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.scheduling; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.aspectj.lang.annotation.Aspect; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -26,7 +26,7 @@ class SpringSchedulingInstrumentationAutoConfiguration { @Bean SpringSchedulingInstrumentationAspect springSchedulingInstrumentationAspect( - OpenTelemetry openTelemetry, ConfigProperties configProperties) { - return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + OpenTelemetry openTelemetry, InstrumentationConfig config) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, config); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java index 0cda4b63d686..4db9b873fece 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateBeanPostProcessor.java @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.web.client.RestTemplate; @@ -15,13 +15,13 @@ final class RestTemplateBeanPostProcessor implements BeanPostProcessor { private final ObjectProvider openTelemetryProvider; - private final ObjectProvider configPropertiesProvider; + private final ObjectProvider configProvider; RestTemplateBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { this.openTelemetryProvider = openTelemetryProvider; - this.configPropertiesProvider = configPropertiesProvider; + this.configProvider = configProvider; } @Override @@ -31,8 +31,6 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { } return RestTemplateInstrumentation.addIfNotPresent( - (RestTemplate) bean, - openTelemetryProvider.getObject(), - configPropertiesProvider.getObject()); + (RestTemplate) bean, openTelemetryProvider.getObject(), configProvider.getObject()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java index 9465c534d0ce..ca4c0c97c466 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestTemplateInstrumentation.java @@ -7,10 +7,10 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.web.v3_1.SpringWebTelemetry; import io.opentelemetry.instrumentation.spring.web.v3_1.internal.WebTelemetryUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.List; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; @@ -21,7 +21,7 @@ private RestTemplateInstrumentation() {} @CanIgnoreReturnValue static RestTemplate addIfNotPresent( - RestTemplate restTemplate, OpenTelemetry openTelemetry, ConfigProperties config) { + RestTemplate restTemplate, OpenTelemetry openTelemetry, InstrumentationConfig config) { ClientHttpRequestInterceptor instrumentationInterceptor = InstrumentationConfigUtil.configureClientBuilder( diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java index 21a18ec8db2b..97d5bd9b52d5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.web.client.RestTemplateCustomizer; @@ -34,16 +34,16 @@ public SpringWebInstrumentationAutoConfiguration() {} @Bean static RestTemplateBeanPostProcessor otelRestTemplateBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { - return new RestTemplateBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + ObjectProvider configProvider) { + return new RestTemplateBeanPostProcessor(openTelemetryProvider, configProvider); } @Bean RestTemplateCustomizer otelRestTemplateCustomizer( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { return restTemplate -> RestTemplateInstrumentation.addIfNotPresent( - restTemplate, openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + restTemplate, openTelemetryProvider.getObject(), configProvider.getObject()); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java index ff6d8f9a5d0b..79bacde3ccc2 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -34,12 +34,12 @@ public SpringWebfluxInstrumentationAutoConfiguration() {} @Bean static WebClientBeanPostProcessor otelWebClientBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { - return new WebClientBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + ObjectProvider configProvider) { + return new WebClientBeanPostProcessor(openTelemetryProvider, configProvider); } @Bean - WebFilter telemetryFilter(OpenTelemetry openTelemetry, ConfigProperties config) { + WebFilter telemetryFilter(OpenTelemetry openTelemetry, InstrumentationConfig config) { return WebClientBeanPostProcessor.getWebfluxServerTelemetry(openTelemetry, config) .createWebFilterAndRegisterReactorHook(); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java index 13a74048d3eb..9b501c410be3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessor.java @@ -6,11 +6,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webflux; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxClientTelemetry; import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxServerTelemetry; import io.opentelemetry.instrumentation.spring.webflux.v5_3.internal.SpringWebfluxBuilderUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.web.reactive.function.client.WebClient; @@ -23,17 +23,17 @@ final class WebClientBeanPostProcessor implements BeanPostProcessor { private final ObjectProvider openTelemetryProvider; - private final ObjectProvider configPropertiesProvider; + private final ObjectProvider configProvider; WebClientBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { this.openTelemetryProvider = openTelemetryProvider; - this.configPropertiesProvider = configPropertiesProvider; + this.configProvider = configProvider; } static SpringWebfluxClientTelemetry getWebfluxClientTelemetry( - OpenTelemetry openTelemetry, ConfigProperties config) { + OpenTelemetry openTelemetry, InstrumentationConfig config) { return InstrumentationConfigUtil.configureClientBuilder( config, SpringWebfluxClientTelemetry.builder(openTelemetry), @@ -42,7 +42,7 @@ static SpringWebfluxClientTelemetry getWebfluxClientTelemetry( } static SpringWebfluxServerTelemetry getWebfluxServerTelemetry( - OpenTelemetry openTelemetry, ConfigProperties config) { + OpenTelemetry openTelemetry, InstrumentationConfig config) { return InstrumentationConfigUtil.configureServerBuilder( config, SpringWebfluxServerTelemetry.builder(openTelemetry), @@ -64,8 +64,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) { private WebClient.Builder wrapBuilder(WebClient.Builder webClientBuilder) { SpringWebfluxClientTelemetry instrumentation = - getWebfluxClientTelemetry( - openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + getWebfluxClientTelemetry(openTelemetryProvider.getObject(), configProvider.getObject()); return webClientBuilder.filters(instrumentation::addFilter); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc5InstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc5InstrumentationAutoConfiguration.java index 8d971c9d4890..73d3f657504c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc5InstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc5InstrumentationAutoConfiguration.java @@ -6,11 +6,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.webmvc.v5_3.SpringWebMvcTelemetry; import io.opentelemetry.instrumentation.spring.webmvc.v5_3.internal.SpringMvcBuilderUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import javax.servlet.Filter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -28,7 +28,7 @@ public class SpringWebMvc5InstrumentationAutoConfiguration { @Bean - Filter otelWebMvcFilter(OpenTelemetry openTelemetry, ConfigProperties config) { + Filter otelWebMvcFilter(OpenTelemetry openTelemetry, InstrumentationConfig config) { return InstrumentationConfigUtil.configureServerBuilder( config, SpringWebMvcTelemetry.builder(openTelemetry), diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java index 047d2ee809ea..dbabca731486 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/ConfigPropertiesBridge.java @@ -5,8 +5,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; +import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; +import static java.util.Objects.requireNonNull; + import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.api.incubator.config.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; @@ -24,9 +28,16 @@ public final class ConfigPropertiesBridge implements InstrumentationConfig { private final ConfigProperties configProperties; + @Nullable private final ConfigProvider configProvider; public ConfigPropertiesBridge(ConfigProperties configProperties) { + this(configProperties, null); + } + + public ConfigPropertiesBridge( + ConfigProperties configProperties, @Nullable ConfigProvider configProvider) { this.configProperties = configProperties; + this.configProvider = configProvider; } @Nullable @@ -113,19 +124,23 @@ public Map getMap(String name, Map defaultValue) @Override public boolean isDeclarative() { - return false; + return configProvider != null; } @Override public DeclarativeConfigProperties getDeclarativeConfig(String node) { - throw new IllegalStateException( - "Declarative configuration is not supported in spring boot autoconfigure yet"); + DeclarativeConfigProperties config = + InstrumentationConfigUtil.javaInstrumentationConfig(requireNonNull(configProvider), node); + if (config == null) { + // there is no declarative config for this node + return empty(); + } + return config; } @Nullable @Override public ConfigProvider getConfigProvider() { - // declarative config support will be added in the future - return null; + return configProvider; } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java index fb5e582bfaa5..683529a18b29 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/InstrumentationConfigUtil.java @@ -9,7 +9,7 @@ import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpClientInstrumenterBuilder; import io.opentelemetry.instrumentation.api.incubator.builder.internal.DefaultHttpServerInstrumenterBuilder; import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import java.util.function.Function; /** @@ -21,7 +21,7 @@ private InstrumentationConfigUtil() {} @CanIgnoreReturnValue public static T configureClientBuilder( - ConfigProperties config, + InstrumentationConfig config, T builder, Function> getBuilder) { getBuilder.apply(builder).configure(getConfig(config)); @@ -30,18 +30,18 @@ public static T configureClientBuilder( @CanIgnoreReturnValue public static T configureServerBuilder( - ConfigProperties config, + InstrumentationConfig config, T builder, Function> getBuilder) { getBuilder.apply(builder).configure(getConfig(config)); return builder; } - private static CommonConfig getConfig(ConfigProperties config) { - return new CommonConfig(new ConfigPropertiesBridge(config)); + private static CommonConfig getConfig(InstrumentationConfig config) { + return new CommonConfig(config); } - public static boolean isStatementSanitizationEnabled(ConfigProperties config, String key) { + public static boolean isStatementSanitizationEnabled(InstrumentationConfig config, String key) { return config.getBoolean( key, config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true)); } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroComponentProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroComponentProvider.java new file mode 100644 index 000000000000..a26f5fa7783b --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroComponentProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class DistroComponentProvider implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "opentelemetry_spring_boot_starter"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + return DistroVersionResourceProvider.get(); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java index 8ba185f97b98..db66ce9f0220 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/DistroVersionResourceProvider.java @@ -28,6 +28,10 @@ public class DistroVersionResourceProvider implements ResourceProvider { @Override public Resource createResource(ConfigProperties config) { + return get(); + } + + static Resource get() { return Resource.create( Attributes.of( TELEMETRY_DISTRO_NAME, diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/ResourceCustomizerProvider.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/ResourceCustomizerProvider.java new file mode 100644 index 000000000000..987402442a52 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/resources/ResourceCustomizerProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.resources; + +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer; +import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectionModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectorModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class ResourceCustomizerProvider implements DeclarativeConfigurationCustomizerProvider { + + // opentelemetry-javaagent-distribution: adds "distro.name" and "distro.version" attributes + // (DistroComponentProvider in this package) + private static final List REQUIRED_DETECTORS = + Collections.singletonList("opentelemetry_spring_boot_starter"); + + @Override + public void customize(DeclarativeConfigurationCustomizer customizer) { + customizer.addModelCustomizer( + model -> { + ResourceModel resource = model.getResource(); + if (resource == null) { + resource = new ResourceModel(); + model.withResource(resource); + } + ExperimentalResourceDetectionModel detectionModel = resource.getDetectionDevelopment(); + if (detectionModel == null) { + detectionModel = new ExperimentalResourceDetectionModel(); + resource.withDetectionDevelopment(detectionModel); + } + List detectors = + Objects.requireNonNull(detectionModel.getDetectors()); + Set names = + detectors.stream() + .flatMap(detector -> detector.getAdditionalProperties().keySet().stream()) + .collect(Collectors.toSet()); + + for (String name : REQUIRED_DETECTORS) { + if (!names.contains(name)) { + ExperimentalResourceDetectorModel detector = new ExperimentalResourceDetectorModel(); + detector.getAdditionalProperties().put(name, null); + // add first (the least precedence) + // so that the user can add a differently named detector that takes precedence + detectors.add(0, detector); + } + } + return model; + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationAccess.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationAccess.java new file mode 100644 index 000000000000..827e9271c6bf --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationAccess.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class DeclarativeConfigurationAccess { + private DeclarativeConfigurationAccess() {} + + public static ObjectMapper getObjectMapper() { + return DeclarativeConfiguration.MAPPER; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java index 8ebefdd655f6..9ef5b7f19194 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java @@ -6,10 +6,10 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.web.v3_1.SpringWebTelemetry; import io.opentelemetry.instrumentation.spring.web.v3_1.internal.WebTelemetryUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -18,26 +18,26 @@ final class RestClientBeanPostProcessor implements BeanPostProcessor { private final ObjectProvider openTelemetryProvider; - private final ObjectProvider configPropertiesProvider; + private final ObjectProvider configProvider; public RestClientBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { this.openTelemetryProvider = openTelemetryProvider; - this.configPropertiesProvider = configPropertiesProvider; + this.configProvider = configProvider; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof RestClient restClient) { return addRestClientInterceptorIfNotPresent( - restClient, openTelemetryProvider.getObject(), configPropertiesProvider.getObject()); + restClient, openTelemetryProvider.getObject(), configProvider.getObject()); } return bean; } private static RestClient addRestClientInterceptorIfNotPresent( - RestClient restClient, OpenTelemetry openTelemetry, ConfigProperties config) { + RestClient restClient, OpenTelemetry openTelemetry, InstrumentationConfig config) { ClientHttpRequestInterceptor instrumentationInterceptor = getInterceptor(openTelemetry, config); return restClient @@ -55,7 +55,7 @@ private static RestClient addRestClientInterceptorIfNotPresent( } static ClientHttpRequestInterceptor getInterceptor( - OpenTelemetry openTelemetry, ConfigProperties config) { + OpenTelemetry openTelemetry, InstrumentationConfig config) { return InstrumentationConfigUtil.configureClientBuilder( config, SpringWebTelemetry.builder(openTelemetry), diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java index a0fad74b0a35..72c83ef5d5ad 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfiguration.java @@ -6,8 +6,8 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -34,17 +34,17 @@ public class RestClientInstrumentationAutoConfiguration { @Bean static RestClientBeanPostProcessor otelRestClientBeanPostProcessor( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { - return new RestClientBeanPostProcessor(openTelemetryProvider, configPropertiesProvider); + ObjectProvider configProvider) { + return new RestClientBeanPostProcessor(openTelemetryProvider, configProvider); } @Bean RestClientCustomizer otelRestClientCustomizer( ObjectProvider openTelemetryProvider, - ObjectProvider configPropertiesProvider) { + ObjectProvider configProvider) { return builder -> builder.requestInterceptor( RestClientBeanPostProcessor.getInterceptor( - openTelemetryProvider.getObject(), configPropertiesProvider.getObject())); + openTelemetryProvider.getObject(), configProvider.getObject())); } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc6InstrumentationAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc6InstrumentationAutoConfiguration.java index 2dc83966bc5f..b8653d4d3936 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc6InstrumentationAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvc6InstrumentationAutoConfiguration.java @@ -6,11 +6,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.webmvc; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.ConditionalOnEnabledInstrumentation; import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.webmvc.v6_0.SpringWebMvcTelemetry; import io.opentelemetry.instrumentation.spring.webmvc.v6_0.internal.SpringMvcBuilderUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import jakarta.servlet.Filter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -28,7 +28,7 @@ public class SpringWebMvc6InstrumentationAutoConfiguration { @Bean - Filter otelWebMvcFilter(OpenTelemetry openTelemetry, ConfigProperties config) { + Filter otelWebMvcFilter(OpenTelemetry openTelemetry, InstrumentationConfig config) { return InstrumentationConfigUtil.configureServerBuilder( config, SpringWebMvcTelemetry.builder(openTelemetry), diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java new file mode 100644 index 000000000000..2396a1147ad3 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/EmbeddedConfigFileTest.java @@ -0,0 +1,214 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class EmbeddedConfigFileTest { + + @Test + void convertFlatPropsToNested_simpleProperties() { + Map flatProps = new HashMap<>(); + flatProps.put("resource.service.name", "my-service"); + flatProps.put("traces.exporter", "otlp"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result) + .containsOnlyKeys("resource", "traces") + .satisfies( + map -> { + assertThat(map.get("resource")) + .isInstanceOf(Map.class) + .satisfies( + resource -> { + @SuppressWarnings("unchecked") + Map resourceMap = (Map) resource; + assertThat(resourceMap) + .containsOnlyKeys("service") + .satisfies( + m -> { + @SuppressWarnings("unchecked") + Map serviceMap = + (Map) m.get("service"); + assertThat(serviceMap).containsEntry("name", "my-service"); + }); + }); + assertThat(map.get("traces")) + .isInstanceOf(Map.class) + .satisfies( + traces -> { + @SuppressWarnings("unchecked") + Map tracesMap = (Map) traces; + assertThat(tracesMap).containsEntry("exporter", "otlp"); + }); + }); + } + + @Test + void convertFlatPropsToNested_arrayProperties() { + Map flatProps = new HashMap<>(); + flatProps.put("instrumentation.java.list[0]", "one"); + flatProps.put("instrumentation.java.list[1]", "two"); + flatProps.put("instrumentation.java.list[2]", "three"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result) + .containsOnlyKeys("instrumentation") + .satisfies( + map -> { + @SuppressWarnings("unchecked") + Map instrumentation = + (Map) map.get("instrumentation"); + assertThat(instrumentation) + .containsOnlyKeys("java") + .satisfies( + m -> { + @SuppressWarnings("unchecked") + Map javaMap = (Map) m.get("java"); + assertThat(javaMap) + .containsOnlyKeys("list") + .satisfies( + java -> { + @SuppressWarnings("unchecked") + List list = (List) java.get("list"); + assertThat(list).containsExactly("one", "two", "three"); + }); + }); + }); + } + + @Test + void convertFlatPropsToNested_mixedPropertiesAndArrays() { + Map flatProps = new HashMap<>(); + flatProps.put("resource.service.name", "test-service"); + flatProps.put("resource.attributes[0]", "key1=value1"); + flatProps.put("resource.attributes[1]", "key2=value2"); + flatProps.put("traces.exporter", "otlp"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result) + .containsOnlyKeys("resource", "traces") + .satisfies( + map -> { + @SuppressWarnings("unchecked") + Map resource = (Map) map.get("resource"); + assertThat(resource) + .containsKeys("service", "attributes") + .satisfies( + r -> { + @SuppressWarnings("unchecked") + Map service = (Map) r.get("service"); + assertThat(service).containsEntry("name", "test-service"); + + @SuppressWarnings("unchecked") + List attributes = (List) r.get("attributes"); + assertThat(attributes).containsExactly("key1=value1", "key2=value2"); + }); + + @SuppressWarnings("unchecked") + Map traces = (Map) map.get("traces"); + assertThat(traces).containsEntry("exporter", "otlp"); + }); + } + + @Test + void convertFlatPropsToNested_emptyMap() { + Map flatProps = new HashMap<>(); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result).isEmpty(); + } + + @Test + void convertFlatPropsToNested_singleLevelProperty() { + Map flatProps = new HashMap<>(); + flatProps.put("enabled", "true"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result).containsEntry("enabled", "true"); + } + + @Test + void convertFlatPropsToNested_arrayWithGaps() { + Map flatProps = new HashMap<>(); + flatProps.put("list[0]", "first"); + flatProps.put("list[2]", "third"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result) + .containsOnlyKeys("list") + .satisfies( + map -> { + @SuppressWarnings("unchecked") + List list = (List) map.get("list"); + assertThat(list).hasSize(3).containsExactly("first", null, "third"); + }); + } + + @Test + void convertFlatPropsToNested_deeplyNestedProperties() { + Map flatProps = new HashMap<>(); + flatProps.put("a.b.c.d.e", "deep-value"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result).containsOnlyKeys("a"); + @SuppressWarnings("unchecked") + Map a = (Map) result.get("a"); + assertThat(a).containsOnlyKeys("b"); + @SuppressWarnings("unchecked") + Map b = (Map) a.get("b"); + assertThat(b).containsOnlyKeys("c"); + @SuppressWarnings("unchecked") + Map c = (Map) b.get("c"); + assertThat(c).containsOnlyKeys("d"); + @SuppressWarnings("unchecked") + Map d = (Map) c.get("d"); + assertThat(d).containsEntry("e", "deep-value"); + } + + @Test + void convertFlatPropsToNested_nestedArrays() { + Map flatProps = new HashMap<>(); + flatProps.put("outer[0].inner[0]", "value1"); + flatProps.put("outer[0].inner[1]", "value2"); + flatProps.put("outer[1].inner[0]", "value3"); + + Map result = EmbeddedConfigFile.convertFlatPropsToNested(flatProps); + + assertThat(result) + .containsOnlyKeys("outer") + .satisfies( + map -> { + @SuppressWarnings("unchecked") + List outer = (List) map.get("outer"); + assertThat(outer).hasSize(2); + + @SuppressWarnings("unchecked") + Map firstElement = (Map) outer.get(0); + @SuppressWarnings("unchecked") + List firstInner = (List) firstElement.get("inner"); + assertThat(firstInner).containsExactly("value1", "value2"); + + @SuppressWarnings("unchecked") + Map secondElement = (Map) outer.get(1); + @SuppressWarnings("unchecked") + List secondInner = (List) secondElement.get("inner"); + assertThat(secondInner).containsExactly("value3"); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 1e1a04bb5f10..be29f798f579 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -26,7 +26,7 @@ /** Spring Boot auto configuration test for {@link OpenTelemetryAutoConfiguration}. */ class OpenTelemetryAutoConfigurationTest { @TestConfiguration - static class CustomTracerConfiguration { + static class CustomOtelConfiguration { @Bean public OpenTelemetry customOpenTelemetry() { return OpenTelemetry.noop(); @@ -48,7 +48,7 @@ public OpenTelemetry customOpenTelemetry() { "when Application Context contains OpenTelemetry bean should NOT initialize openTelemetry") void customOpenTelemetry() { this.contextRunner - .withUserConfiguration(CustomTracerConfiguration.class) + .withUserConfiguration(CustomOtelConfiguration.class) .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) .run( context -> @@ -152,10 +152,8 @@ void shouldInitializeSdkWhenNotDisabled() { .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) .withPropertyValues("otel.sdk.disabled=false") .run( - context -> { - assertThat(context).getBean("openTelemetry").isInstanceOf(OpenTelemetrySdk.class); - assertThat(context).hasBean("openTelemetry"); - }); + context -> + assertThat(context).getBean("openTelemetry").isInstanceOf(OpenTelemetrySdk.class)); } @Test diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java index e269ea83693c..a81051228f98 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/jdbc/JdbcInstrumentationAutoConfigurationTest.java @@ -10,8 +10,9 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.sql.Connection; import java.sql.Statement; @@ -33,8 +34,10 @@ class JdbcInstrumentationAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of( JdbcInstrumentationAutoConfiguration.class, DataSourceAutoConfiguration.class)) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfigurationTest.java index c3e49519eca1..b2671f3daf29 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfigurationTest.java @@ -9,9 +9,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; -import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -19,9 +16,6 @@ class LoggingExporterAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() - .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) .withConfiguration( AutoConfigurations.of( LoggingExporterAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java index ffdd3c7d0181..bed063c28d1f 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/r2dbc/R2DbcInstrumentationAutoConfigurationTest.java @@ -9,8 +9,9 @@ import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -28,8 +29,10 @@ class R2DbcInstrumentationAutoConfigurationTest { private final ApplicationContextRunner runner = new ApplicationContextRunner() .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of( R2dbcInstrumentationAutoConfiguration.class, R2dbcAutoConfiguration.class)) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java index d5fdb35b2bb8..3306b280869c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAspectTest.java @@ -13,9 +13,10 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.sdk.trace.data.StatusData; @@ -39,8 +40,8 @@ class SchedulingInstrumentationAspectTest { private String unproxiedTesterClassName; SpringSchedulingInstrumentationAspect newAspect( - OpenTelemetry openTelemetry, ConfigProperties configProperties) { - return new SpringSchedulingInstrumentationAspect(openTelemetry, configProperties); + OpenTelemetry openTelemetry, InstrumentationConfig config) { + return new SpringSchedulingInstrumentationAspect(openTelemetry, config); } @BeforeEach @@ -56,7 +57,8 @@ void setup() { SpringSchedulingInstrumentationAspect aspect = newAspect( testing.getOpenTelemetry(), - DefaultConfigProperties.createFromMap(Collections.emptyMap())); + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))); factory.addAspect(aspect); schedulingTester = factory.getProxy(); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java index 37d91b411d5a..5b631daf1434 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/scheduling/SchedulingInstrumentationAutoConfigurationTest.java @@ -8,7 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -20,8 +21,10 @@ class SchedulingInstrumentationAutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of(SpringSchedulingInstrumentationAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java index 53bbd882a6d2..8e08dec0ea20 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/SpringWebInstrumentationAutoConfigurationTest.java @@ -8,7 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -22,8 +23,10 @@ class SpringWebInstrumentationAutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withBean(RestTemplate.class, RestTemplate::new) .withConfiguration( AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java index d75814732785..df6273d4686c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/SpringWebfluxInstrumentationAutoConfigurationTest.java @@ -8,7 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -21,8 +22,10 @@ class SpringWebfluxInstrumentationAutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of(SpringWebfluxInstrumentationAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java index 821f0fbfb647..4c25da12a112 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webflux/WebClientBeanPostProcessorTest.java @@ -8,7 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.DisplayName; @@ -24,7 +25,8 @@ class WebClientBeanPostProcessorTest { static { beanFactory.registerSingleton("openTelemetry", OpenTelemetry.noop()); beanFactory.registerSingleton( - "configProperties", DefaultConfigProperties.createFromMap(Collections.emptyMap())); + "configProperties", + new ConfigPropertiesBridge(DefaultConfigProperties.createFromMap(Collections.emptyMap()))); } @Test @@ -34,7 +36,7 @@ void returnsObject() { BeanPostProcessor underTest = new WebClientBeanPostProcessor( beanFactory.getBeanProvider(OpenTelemetry.class), - beanFactory.getBeanProvider(ConfigProperties.class)); + beanFactory.getBeanProvider(InstrumentationConfig.class)); assertThat(underTest.postProcessAfterInitialization(new Object(), "testObject")) .isExactlyInstanceOf(Object.class); @@ -46,7 +48,7 @@ void returnsWebClient() { BeanPostProcessor underTest = new WebClientBeanPostProcessor( beanFactory.getBeanProvider(OpenTelemetry.class), - beanFactory.getBeanProvider(ConfigProperties.class)); + beanFactory.getBeanProvider(InstrumentationConfig.class)); assertThat(underTest.postProcessAfterInitialization(WebClient.create(), "testWebClient")) .isInstanceOf(WebClient.class); @@ -58,7 +60,7 @@ void returnsWebClientBuilder() { BeanPostProcessor underTest = new WebClientBeanPostProcessor( beanFactory.getBeanProvider(OpenTelemetry.class), - beanFactory.getBeanProvider(ConfigProperties.class)); + beanFactory.getBeanProvider(InstrumentationConfig.class)); assertThat( underTest.postProcessAfterInitialization(WebClient.builder(), "testWebClientBuilder")) @@ -71,7 +73,7 @@ void addsExchangeFilterWebClient() { BeanPostProcessor underTest = new WebClientBeanPostProcessor( beanFactory.getBeanProvider(OpenTelemetry.class), - beanFactory.getBeanProvider(ConfigProperties.class)); + beanFactory.getBeanProvider(InstrumentationConfig.class)); WebClient webClient = WebClient.create(); Object processedWebClient = @@ -96,7 +98,7 @@ void addsExchangeFilterWebClientBuilder() { BeanPostProcessor underTest = new WebClientBeanPostProcessor( beanFactory.getBeanProvider(OpenTelemetry.class), - beanFactory.getBeanProvider(ConfigProperties.class)); + beanFactory.getBeanProvider(InstrumentationConfig.class)); WebClient.Builder webClientBuilder = WebClient.builder(); underTest.postProcessAfterInitialization(webClientBuilder, "testWebClientBuilder"); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java index 62112774516e..3c90a46fdfdc 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation5AutoConfigurationTest.java @@ -9,7 +9,8 @@ import static org.junit.jupiter.api.Assumptions.assumeFalse; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import javax.servlet.Filter; @@ -24,8 +25,10 @@ class SpringWebMvcInstrumentation5AutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of(SpringWebMvc5InstrumentationAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java index c8afd35d5aa6..754373e35f6d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java @@ -92,9 +92,9 @@ void mapFlatHeadersWithUserSuppliedOtelBean(String key) { .withSystemProperties(key + "=a=1,b=2") .withBean(OpenTelemetry.class, OpenTelemetry::noop) .run( - context -> - assertThat(getConfig(context).getMap(key)) - .containsExactly(entry("a", "1"), entry("b", "2"))); + context -> { + // don't crash if OpenTelemetry bean is provided + }); } @ParameterizedTest diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java new file mode 100644 index 000000000000..d15468a17944 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/java/io/opentelemetry/instrumentation/spring/autoconfigure/DeclarativeConfigTest.java @@ -0,0 +1,134 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.web.SpringWebInstrumentationAutoConfiguration; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; + +/** Spring Boot auto configuration test for {@link OpenTelemetryAutoConfiguration}. */ +class DeclarativeConfigTest { + @TestConfiguration + static class CustomTracerConfiguration { + @Bean + public OpenTelemetry customOpenTelemetry() { + return OpenTelemetry.noop(); + } + } + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + // to load application.yaml + .withInitializer(new ConfigDataApplicationContextInitializer()); + + @Test + @DisplayName( + "when Application Context contains OpenTelemetry bean should NOT initialize openTelemetry") + void customOpenTelemetry() { + this.contextRunner + .withUserConfiguration(CustomTracerConfiguration.class) + .withPropertyValues("otel.file_format=1.0-rc.1") + .run( + context -> + assertThat(context) + .hasBean("customOpenTelemetry") + .doesNotHaveBean("openTelemetry") + .hasBean("otelProperties")); + } + + @Test + @DisplayName( + "when Application Context DOES NOT contain OpenTelemetry bean should initialize openTelemetry") + void initializeProvidersAndOpenTelemetry() { + this.contextRunner.run( + context -> + assertThat(context) + .hasBean("openTelemetry") + .hasBean("otelProperties") + .getBean(InstrumentationConfig.class) + .isNotNull() + .satisfies( + c -> + assertThat(c.getDeclarativeConfig("foo")) + .isNotNull() + .satisfies( + instrumentationConfig -> + assertThat(instrumentationConfig.getString("bar")) + .isEqualTo("baz")))); + } + + @Test + void shouldInitializeSdkWhenNotDisabled() { + this.contextRunner + .withPropertyValues("otel.file_format=1.0-rc.1", "otel.disabled=false") + .run( + context -> + assertThat(context).getBean("openTelemetry").isInstanceOf(OpenTelemetrySdk.class)); + } + + @Test + void shouldInitializeNoopOpenTelemetryWhenSdkIsDisabled() { + this.contextRunner + .withPropertyValues( + "otel.file_format=1.0-rc.1", + "otel.disabled=true", + "otel.resource.attributes=service.name=workflow-backend-dev,service.version=3c8f9ce9") + .run( + context -> + assertThat(context).getBean("openTelemetry").isEqualTo(OpenTelemetry.noop())); + } + + @Test + void shouldLoadInstrumentation() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)) + .withPropertyValues("otel.file_format=1.0-rc.1") + .run(context -> assertThat(context).hasBean("otelRestTemplateBeanPostProcessor")); + } + + @Test + void shouldNotLoadInstrumentationWhenDefaultIsDisabled() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)) + .withPropertyValues( + "otel.file_format=1.0-rc.1", + "otel.instrumentation/development.java.spring_starter.instrumentation_mode=none") + .run(context -> assertThat(context).doesNotHaveBean("otelRestTemplateBeanPostProcessor")); + } + + @Test + void shouldLoadInstrumentationWhenExplicitlyEnabled() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)) + .withPropertyValues( + "otel.file_format=1.0-rc.1", + "otel.instrumentation/development.java.spring_starter.instrumentation_mode=none", + "otel.instrumentation/development.java.spring_web.enabled=true") + .run(context -> assertThat(context).hasBean("otelRestTemplateBeanPostProcessor")); + } + + @Test + void shouldNotLoadInstrumentationWhenExplicitlyDisabled() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringWebInstrumentationAutoConfiguration.class)) + .withPropertyValues( + "otel.file_format=1.0-rc.1", + "otel.instrumentation/development.java.spring_starter.instrumentation_mode=none", + "otel.instrumentation/development.java.spring_web.enabled=false") + .run(context -> assertThat(context).doesNotHaveBean("otelRestTemplateBeanPostProcessor")); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml new file mode 100644 index 000000000000..8f263bc8ff50 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testDeclarativeConfig/resources/application.yaml @@ -0,0 +1,10 @@ +otel: + # "file_format" serves as opt-in to the new file format + file_format: "1.0-rc.1" + + # very lightweight test to make sure the declarative config is loaded + # the full config is tested in smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig + instrumentation/development: + java: + foo: + bar: baz diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java index bae340b948ef..3e16f15b23ef 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java @@ -8,7 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -22,8 +23,10 @@ class RestClientInstrumentationAutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withBean(RestClient.class, RestClient::create) .withConfiguration( AutoConfigurations.of(RestClientInstrumentationAutoConfiguration.class)); diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java index d064262095c6..a96d6abc2f4e 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/webmvc/SpringWebMvcInstrumentation6AutoConfigurationTest.java @@ -9,7 +9,8 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.instrumentation.api.incubator.config.internal.InstrumentationConfig; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.ConfigPropertiesBridge; import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import jakarta.servlet.Filter; import java.util.Collections; @@ -24,8 +25,10 @@ class SpringWebMvcInstrumentation6AutoConfigurationTest { new ApplicationContextRunner() .withBean(OpenTelemetry.class, OpenTelemetry::noop) .withBean( - ConfigProperties.class, - () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + InstrumentationConfig.class, + () -> + new ConfigPropertiesBridge( + DefaultConfigProperties.createFromMap(Collections.emptyMap()))) .withConfiguration( AutoConfigurations.of(SpringWebMvc6InstrumentationAutoConfiguration.class)); diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java index 035799c06d47..5c7fe70d9f1d 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/config/ConfigPropertiesBridge.java @@ -6,6 +6,7 @@ package io.opentelemetry.javaagent.tooling.config; import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty; +import static java.util.Objects.requireNonNull; import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; @@ -119,11 +120,9 @@ public boolean isDeclarative() { @Override public DeclarativeConfigProperties getDeclarativeConfig(String node) { DeclarativeConfigProperties config = - InstrumentationConfigUtil.javaInstrumentationConfig(configProvider, node); + InstrumentationConfigUtil.javaInstrumentationConfig(requireNonNull(configProvider), node); if (config == null) { // there is no declarative config for this node - // this needs to be a different value than null to avoid confusion with - // the case when declarative config is not supported at all return empty(); } return config; diff --git a/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts b/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts index 3aed6f536441..ca6a1752ef0d 100644 --- a/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts +++ b/smoke-tests-otel-starter/spring-boot-2/build.gradle.kts @@ -31,3 +31,22 @@ configurations.configureEach { force("org.slf4j:slf4j-api:1.7.36") } } + +testing { + suites { + val testDeclarativeConfig by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":smoke-tests-otel-starter:spring-boot-common")) + implementation("org.springframework.boot:spring-boot-starter-web:2.6.15") + implementation("org.springframework.boot:spring-boot-starter-test:2.6.15") + } + } + } +} + +tasks { + check { + dependsOn(testing.suites) + } +} diff --git a/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java b/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java new file mode 100644 index 000000000000..2e0e2d14d6fb --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/java/io/opentelemetry/spring/smoketest/OtelSpringStarterSmokeTest.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.spring.smoketest; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServiceAttributes; +import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes; +import io.opentelemetry.semconv.incubating.TelemetryIncubatingAttributes; +import org.assertj.core.api.AbstractCharSequenceAssert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest( + classes = { + OtelSpringStarterSmokeTestApplication.class, + AbstractOtelSpringStarterSmokeTest.TestConfiguration.class, + SpringSmokeOtelConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) +class OtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest { + + @Autowired private RestTemplateBuilder restTemplateBuilder; + + // can't use @LocalServerPort annotation since it moved packages between Spring Boot 2 and 3 + @Value("${local.server.port}") + private int port; + + @Test + void restTemplate() { + testing.clearAllExportedData(); + + RestTemplate restTemplate = restTemplateBuilder.rootUri("http://localhost:" + port).build(); + restTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class); + testing.waitAndAssertTraces( + traceAssert -> + traceAssert.hasSpansSatisfyingExactly( + span -> + HttpSpanDataAssert.create(span) + .assertClientGetRequest("/ping") + .hasResourceSatisfying( + r -> + r.hasAttribute( + // to make sure the declarative config is picked up + // in application.yaml + ServiceAttributes.SERVICE_NAME, + "declarative-config-spring-boot-2")), + span -> + span.hasKind(SpanKind.SERVER) + .hasResourceSatisfying( + r -> + r.hasAttribute( + TelemetryIncubatingAttributes.TELEMETRY_DISTRO_NAME, + "opentelemetry-spring-boot-starter") + .hasAttribute( + satisfies( + TelemetryIncubatingAttributes.TELEMETRY_DISTRO_VERSION, + AbstractCharSequenceAssert::isNotBlank)) + .hasAttribute( + satisfies( + ServiceIncubatingAttributes.SERVICE_INSTANCE_ID, + AbstractCharSequenceAssert::isNotBlank))) + .hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping"), + AbstractSpringStarterSmokeTest::withSpanAssert)); + } +} diff --git a/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/resources/application.yaml b/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/resources/application.yaml new file mode 100644 index 000000000000..6b24c0ee67fa --- /dev/null +++ b/smoke-tests-otel-starter/spring-boot-2/src/testDeclarativeConfig/resources/application.yaml @@ -0,0 +1,47 @@ +otel: + # "file_format" serves as opt-in to the new file format + file_format: "1.0-rc.1" + resource: + detection/development: + detectors: + - service: + attributes: + - name: service.name + value: declarative-config-spring-boot-2 + + tracer_provider: + processors: + - simple: + exporter: + test: + - simple: + exporter: + console: + + logger_provider: + processors: + - simple: + exporter: + test: + + meter_provider: + readers: + - periodic: + # Set really long interval. We'll call forceFlush when we need the metrics + # instead of collecting them periodically. + interval: 1000000 + exporter: + test: + + propagator: + composite: + - tracecontext: + - baggage: + + instrumentation/development: + java: + runtime-telemetry: + emit_experimental_telemetry: true + http: + client: + emit_experimental_telemetry: true diff --git a/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/SpringSmokeTestRunner.java b/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/SpringSmokeTestRunner.java index 471016ee883e..0675f3b0b61a 100644 --- a/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/SpringSmokeTestRunner.java +++ b/smoke-tests-otel-starter/spring-smoke-testing/src/main/java/io/opentelemetry/spring/smoketest/SpringSmokeTestRunner.java @@ -7,6 +7,9 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner; +import io.opentelemetry.instrumentation.testing.provider.TestLogRecordExporterComponentProvider; +import io.opentelemetry.instrumentation.testing.provider.TestMetricExporterComponentProvider; +import io.opentelemetry.instrumentation.testing.provider.TestSpanExporterComponentProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; @@ -36,6 +39,11 @@ static void resetExporters() { testSpanExporter = InMemorySpanExporter.create(); testMetricExporter = InMemoryMetricExporter.create(AggregationTemporality.DELTA); testLogRecordExporter = InMemoryLogRecordExporter.create(); + + // for declarative config + TestLogRecordExporterComponentProvider.setLogRecordExporter(testLogRecordExporter); + TestMetricExporterComponentProvider.setMetricExporter(testMetricExporter); + TestSpanExporterComponentProvider.setSpanExporter(testSpanExporter); } @Override