diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/AutoConfigureUtil2.java b/maven-extension/src/main/java/io/opentelemetry/maven/AutoConfigureUtil2.java new file mode 100644 index 000000000..6ebcf004b --- /dev/null +++ b/maven-extension/src/main/java/io/opentelemetry/maven/AutoConfigureUtil2.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven; + +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** Utility class to use the {@link AutoConfiguredOpenTelemetrySdk}. */ +public class AutoConfigureUtil2 { + + private AutoConfigureUtil2() {} + + /** + * Returns the {@link Resource} that was autoconfigured. + * + *

Inspired by {@link + * io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil#getConfig(AutoConfiguredOpenTelemetrySdk)} + */ + public static Resource getResource( + AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) { + try { + Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource"); + method.setAccessible(true); + return (Resource) method.invoke(autoConfiguredOpenTelemetrySdk); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException( + "Error calling getResource on AutoConfiguredOpenTelemetrySdk", e); + } + } +} diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java b/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java index af4fe9662..a357b3177 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java @@ -5,19 +5,24 @@ package io.opentelemetry.maven; +import com.google.common.annotations.VisibleForTesting; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.resources.Resource; import java.io.Closeable; +import java.util.Collections; import java.util.HashMap; -import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; import javax.annotation.PreDestroy; import javax.inject.Named; import javax.inject.Singleton; @@ -36,6 +41,10 @@ public final class OpenTelemetrySdkService implements Closeable { private final OpenTelemetrySdk openTelemetrySdk; + @VisibleForTesting final Resource resource; + + private final ConfigProperties configProperties; + private final Tracer tracer; private final boolean mojosInstrumentationEnabled; @@ -47,32 +56,68 @@ public OpenTelemetrySdkService() { "OpenTelemetry: Initialize OpenTelemetrySdkService v{}...", MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE); - // Change default of "otel.[traces,metrics,logs].exporter" from "otlp" to "none" - // The impacts are - // * If no otel exporter settings are passed, then the Maven extension will not export - // rather than exporting on OTLP GRPC to http://localhost:4317 - // * If OTEL_EXPORTER_OTLP_ENDPOINT is defined but OTEL_[TRACES,METRICS,LOGS]_EXPORTER, - // is not, then don't export - Map properties = new HashMap<>(); - properties.put("otel.traces.exporter", "none"); - properties.put("otel.metrics.exporter", "none"); - properties.put("otel.logs.exporter", "none"); - AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk = AutoConfiguredOpenTelemetrySdk.builder() .setServiceClassLoader(getClass().getClassLoader()) - .addPropertiesSupplier(() -> properties) + .addPropertiesCustomizer( + OpenTelemetrySdkService::requireExplicitConfigOfTheOtlpExporter) .disableShutdownHook() .build(); this.openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk(); + this.configProperties = + Optional.ofNullable(AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk)) + .orElseGet(() -> DefaultConfigProperties.createFromMap(Collections.emptyMap())); + + this.resource = AutoConfigureUtil2.getResource(autoConfiguredOpenTelemetrySdk); + // Display resource attributes in debug logs for troubleshooting when traces are not found in + // the observability backend, helping understand `service.name`, `service.namespace`, etc. + logger.debug("OpenTelemetry: OpenTelemetrySdkService initialized, resource:{}", resource); - Boolean mojoSpansEnabled = getBooleanConfig("otel.instrumentation.maven.mojo.enabled"); - this.mojosInstrumentationEnabled = mojoSpansEnabled == null || mojoSpansEnabled; + this.mojosInstrumentationEnabled = + configProperties.getBoolean("otel.instrumentation.maven.mojo.enabled", true); this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION); } + /** + * The OTel SDK by default sends data to the OTLP gRPC endpoint localhost:4317 if no exporter and + * no OTLP exporter endpoint are defined. This is not suited for a build tool for which we want + * the OTel SDK to be disabled by default. + * + *

Change the OTel SDL behavior: if none of the exporter and the OTLP exporter endpoint are + * defined, explicitly disable the exporter setting "{@code + * otel.[traces,metrics,logs].exporter=none}" + * + * @return The properties to be returned by {@link + * io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder#addPropertiesCustomizer(java.util.function.Function)} + */ + static Map requireExplicitConfigOfTheOtlpExporter( + ConfigProperties configProperties) { + + Map properties = new HashMap<>(); + if (configProperties.getString("otel.exporter.otlp.endpoint") != null) { + logger.debug("OpenTelemetry: OTLP exporter endpoint is explicitly configured"); + return properties; + } + String[] signalTypes = {"traces", "metrics", "logs"}; + for (String signalType : signalTypes) { + boolean isExporterImplicitlyConfiguredToOtlp = + configProperties.getString("otel." + signalType + ".exporter") == null; + boolean isOtlpExporterEndpointSpecified = + configProperties.getString("otel.exporter.otlp." + signalType + ".endpoint") != null; + + if (isExporterImplicitlyConfiguredToOtlp && !isOtlpExporterEndpointSpecified) { + logger.debug( + "OpenTelemetry: Disabling default OTLP exporter endpoint for signal {} exporter", + signalType); + properties.put("otel." + signalType + ".exporter", "none"); + } + } + + return properties; + } + @PreDestroy @Override public synchronized void close() { @@ -97,6 +142,10 @@ public Tracer getTracer() { return this.tracer; } + public ConfigProperties getConfigProperties() { + return configProperties; + } + /** Returns the {@link ContextPropagators} for this {@link OpenTelemetry}. */ public ContextPropagators getPropagators() { return this.openTelemetrySdk.getPropagators(); @@ -105,17 +154,4 @@ public ContextPropagators getPropagators() { public boolean isMojosInstrumentationEnabled() { return mojosInstrumentationEnabled; } - - @Nullable - private static Boolean getBooleanConfig(String name) { - String value = System.getProperty(name); - if (value != null) { - return Boolean.parseBoolean(value); - } - value = System.getenv(name.toUpperCase(Locale.ROOT).replace('.', '_')); - if (value != null) { - return Boolean.parseBoolean(value); - } - return null; - } } diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/AutoConfigureUtil2Test.java b/maven-extension/src/test/java/io/opentelemetry/maven/AutoConfigureUtil2Test.java new file mode 100644 index 000000000..4a72de43e --- /dev/null +++ b/maven-extension/src/test/java/io/opentelemetry/maven/AutoConfigureUtil2Test.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven; + +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import java.lang.reflect.Method; +import org.junit.jupiter.api.Test; + +class AutoConfigureUtil2Test { + + /** + * Verify the reflection call works with the current version of AutoConfiguredOpenTelemetrySdk. + * + * @throws NoSuchMethodException if the method does not exist + */ + @Test + void test_getResource() throws NoSuchMethodException { + Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource"); + method.setAccessible(true); + } +} diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java index 0b7b41a31..0174a6283 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java @@ -5,42 +5,132 @@ package io.opentelemetry.maven; -import org.junit.jupiter.api.Disabled; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +/** + * Note: if otel-java-contrib bumps to Java 11+, we could use junit-pioneer's + * {@code @SetSystemProperty} and {@code @ClearSystemProperty} but no bump is planned for now. + */ public class OpenTelemetrySdkServiceTest { - /** Verify default `service.name` */ + /** Verify default config */ @Test - @Disabled public void testDefaultConfiguration() { - testConfiguration("maven"); + System.clearProperty("otel.exporter.otlp.endpoint"); + System.clearProperty("otel.service.name"); + System.clearProperty("otel.resource.attributes"); + try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { + + Resource resource = openTelemetrySdkService.resource; + assertThat(resource.getAttribute(stringKey("service.name"))).isEqualTo("maven"); + + ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties(); + assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); + assertThat(configProperties.getString("otel.traces.exporter")).isEqualTo("none"); + assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none"); + assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none"); + } } - /** Verify overwritten `service.name` */ + /** Verify overwritten `service.name`,`key1` and `key2` */ @Test - @Disabled - public void testOverwrittenConfiguration() { + public void testOverwrittenResourceAttributes() { System.setProperty("otel.service.name", "my-maven"); - try { - testConfiguration("my-maven"); + System.setProperty("otel.resource.attributes", "key1=val1,key2=val2"); + + try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { + + Resource resource = openTelemetrySdkService.resource; + assertThat(resource.getAttribute(stringKey("service.name"))).isEqualTo("my-maven"); + assertThat(resource.getAttribute(stringKey("key1"))).isEqualTo("val1"); + assertThat(resource.getAttribute(stringKey("key2"))).isEqualTo("val2"); + } finally { System.clearProperty("otel.service.name"); + System.clearProperty("otel.resource.attributes"); + } + } + + /** Verify defining `otel.exporter.otlp.endpoint` works */ + @Test + public void testOverwrittenExporterConfiguration_1() { + System.setProperty("otel.exporter.otlp.endpoint", "https://example.com:4317"); + + try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { + + ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties(); + assertThat(configProperties.getString("otel.exporter.otlp.endpoint")) + .isEqualTo("https://example.com:4317"); + assertThat(configProperties.getString("otel.traces.exporter")).isNull(); + assertThat(configProperties.getString("otel.metrics.exporter")).isNull(); + assertThat(configProperties.getString("otel.logs.exporter")).isNull(); + + } finally { + System.clearProperty("otel.exporter.otlp.endpoint"); + } + } + + /** Verify defining `otel.exporter.otlp.traces.endpoint` works */ + @Test + public void testOverwrittenExporterConfiguration_2() { + System.clearProperty("otel.exporter.otlp.endpoint"); + System.clearProperty("otel.traces.exporter"); + System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/"); + + try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { + + ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties(); + assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); + assertThat(configProperties.getString("otel.exporter.otlp.traces.endpoint")) + .isEqualTo("https://example.com:4317/"); + assertThat(configProperties.getString("otel.traces.exporter")).isNull(); + assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none"); + assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none"); + + } finally { + System.clearProperty("otel.exporter.otlp.endpoint"); + System.clearProperty("otel.traces.exporter"); + System.clearProperty("otel.exporter.otlp.traces.endpoint"); + } + } + + /** Verify defining `otel.exporter.otlp.traces.endpoint` and `otel.traces.exporter` works */ + @Test + public void testOverwrittenExporterConfiguration_3() { + System.clearProperty("otel.exporter.otlp.endpoint"); + System.setProperty("otel.traces.exporter", "otlp"); + System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/"); + + try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { + + ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties(); + assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull(); + assertThat(configProperties.getString("otel.exporter.otlp.traces.endpoint")) + .isEqualTo("https://example.com:4317/"); + assertThat(configProperties.getString("otel.traces.exporter")).isEqualTo("otlp"); + assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none"); + assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none"); + + } finally { + System.clearProperty("otel.exporter.otlp.endpoint"); + System.clearProperty("otel.exporter.otlp.traces.endpoint"); + System.clearProperty("otel.exporter.otlp.traces.protocol"); } } - void testConfiguration(String expectedServiceName) { - // OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService(); - // openTelemetrySdkService.initialize(); - // try { - // Resource resource = - // openTelemetrySdkService.autoConfiguredOpenTelemetrySdk.getResource(); - // assertThat(resource.getAttribute(ResourceAttributes.SERVICE_NAME)) - // .isEqualTo(expectedServiceName); - // } finally { - // openTelemetrySdkService.dispose(); - // GlobalOpenTelemetry.resetForTest(); - // GlobalEventEmitterProvider.resetForTest(); - // } + @AfterAll + static void afterAll() { + System.clearProperty("otel.exporter.otlp.endpoint"); + System.clearProperty("otel.exporter.otlp.traces.endpoint"); + System.clearProperty("otel.exporter.otlp.traces.protocol"); + System.clearProperty("otel.resource.attributes"); + System.clearProperty("otel.service.name"); + System.clearProperty("otel.traces.exporter"); } }