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 new file mode 100644 index 000000000000..0638941a18af --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import static java.util.Collections.emptyList; + +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@Conditional(SdkEnabled.class) +// to match "otel.javaagent.debug" system property +@ConditionalOnProperty(name = "otel.spring-starter.debug", havingValue = "true") +@ConditionalOnClass(LoggingSpanExporter.class) +@Configuration +public class LoggingExporterAutoConfiguration { + + @Bean + public AutoConfigurationCustomizerProvider loggingOtelCustomizer() { + return p -> + p.addTracerProviderCustomizer( + (builder, config) -> { + enableLoggingExporter(builder, config); + return builder; + }); + } + + public static void enableLoggingExporter( + SdkTracerProviderBuilder builder, ConfigProperties config) { + // don't install another instance if the user has already explicitly requested it. + if (loggingExporterIsNotAlreadyConfigured(config)) { + builder.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create())); + } + } + + private static boolean loggingExporterIsNotAlreadyConfigured(ConfigProperties config) { + return !config.getList("otel.traces.exporter", emptyList()).contains("logging"); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 75d3ef14f304..e92a74381443 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -295,6 +295,12 @@ "type": "java.util.Map", "description": "Used to specify a mapping from host names or IP addresses to peer services, as a comma-separated list of host_or_ip=user_assigned_name pairs. The peer service is added as an attribute to a span whose host or IP address match the mapping. See https://opentelemetry.io/docs/zero-code/java/agent/configuration/#peer-service-name." }, + { + "name": "otel.instrumentation.common.thread-details.enabled", + "type": "java.lang.Boolean", + "description": "Enable the capture of thread details (thread id and thread name) as span attributes.", + "defaultValue": false + }, { "name": "otel.instrumentation.http.client.capture-request-headers", "type": "java.util.List", @@ -627,6 +633,12 @@ "description": "The maximum number of links per span.", "defaultValue": 128 }, + { + "name": "otel.spring-starter.debug", + "type": "java.lang.Boolean", + "description": "Print spans to console for debugging purposes.", + "defaultValue": false + }, { "name": "otel.traces.exporter", "type": "java.util.List", 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 new file mode 100644 index 000000000000..c3e49519eca1 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/logging/LoggingExporterAutoConfigurationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.instrumentation.logging; + +import static org.assertj.core.api.Assertions.assertThat; + +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; + +class LoggingExporterAutoConfigurationTest { + private final ApplicationContextRunner runner = + new ApplicationContextRunner() + .withBean( + ConfigProperties.class, + () -> DefaultConfigProperties.createFromMap(Collections.emptyMap())) + .withConfiguration( + AutoConfigurations.of( + LoggingExporterAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); + + @Test + void debugEnabled() { + runner + .withPropertyValues("otel.spring-starter.debug=true", "otel.traces.exporter=none") + .run( + context -> + assertThat(context.getBean(OpenTelemetry.class).toString()) + .containsOnlyOnce("LoggingSpanExporter")); + } + + @Test + void alreadyAdded() { + runner + .withPropertyValues("otel.spring-starter.debug=true", "otel.traces.exporter=logging") + .run( + context -> + assertThat(context.getBean(OpenTelemetry.class).toString()) + .containsOnlyOnce("LoggingSpanExporter")); + } + + @Test + void debugUnset() { + runner.run( + context -> + assertThat(context.getBean(OpenTelemetry.class).toString()) + .doesNotContain("LoggingSpanExporter")); + } + + @Test + void debugDisabled() { + runner + .withPropertyValues("otel.spring-starter.debug=false", "otel.traces.exporter=none") + .run( + context -> + assertThat(context.getBean(OpenTelemetry.class).toString()) + .doesNotContain("LoggingSpanExporter")); + } +}