diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts index a7977b5e82a..1a6cd4d089f 100644 --- a/sdk-extensions/incubator/build.gradle.kts +++ b/sdk-extensions/incubator/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { testImplementation(project(":sdk:testing")) testImplementation(project(":sdk-extensions:autoconfigure")) testImplementation(project(":exporters:otlp:all")) + testImplementation(project(":sdk-extensions:jaeger-remote-sampler")) testImplementation("com.linecorp.armeria:armeria-junit5") testImplementation("com.google.guava:guava-testlib") diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java index 3aacd194073..f1c882a48b9 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java @@ -14,8 +14,10 @@ final class FileConfigUtil { private FileConfigUtil() {} /** Add the {@code closeable} to the {@code closeables} and return it. */ - static T addAndReturn(List closeables, T closeable) { - closeables.add(closeable); + static T addAndReturn(List closeables, T closeable) { + if (closeable instanceof Closeable) { + closeables.add((Closeable) closeable); + } return closeable; } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactory.java new file mode 100644 index 00000000000..06e13f69429 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static java.util.stream.Collectors.joining; + +import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.JaegerRemote; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ParentBased; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TraceIdRatioBased; +import io.opentelemetry.sdk.trace.samplers.ParentBasedSamplerBuilder; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.io.Closeable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +final class SamplerFactory + implements Factory< + io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler, Sampler> { + + private static final SamplerFactory INSTANCE = new SamplerFactory(); + + private SamplerFactory() {} + + static SamplerFactory getInstance() { + return INSTANCE; + } + + @Override + public Sampler create( + @Nullable io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler model, + SpiHelper spiHelper, + List closeables) { + if (model == null) { + return Sampler.parentBased(Sampler.alwaysOn()); + } + + if (model.getAlwaysOn() != null) { + return Sampler.alwaysOn(); + } + if (model.getAlwaysOff() != null) { + return Sampler.alwaysOff(); + } + TraceIdRatioBased traceIdRatioBasedModel = model.getTraceIdRatioBased(); + if (traceIdRatioBasedModel != null) { + Double ratio = traceIdRatioBasedModel.getRatio(); + if (ratio == null) { + ratio = 1.0d; + } + return Sampler.traceIdRatioBased(ratio); + } + ParentBased parentBasedModel = model.getParentBased(); + if (parentBasedModel != null) { + Sampler root = + parentBasedModel.getRoot() == null + ? Sampler.alwaysOn() + : create(parentBasedModel.getRoot(), spiHelper, closeables); + ParentBasedSamplerBuilder builder = Sampler.parentBasedBuilder(root); + if (parentBasedModel.getRemoteParentSampled() != null) { + builder.setRemoteParentSampled( + create(parentBasedModel.getRemoteParentSampled(), spiHelper, closeables)); + } + if (parentBasedModel.getRemoteParentNotSampled() != null) { + builder.setRemoteParentNotSampled( + create(parentBasedModel.getRemoteParentNotSampled(), spiHelper, closeables)); + } + if (parentBasedModel.getLocalParentSampled() != null) { + builder.setLocalParentSampled( + create(parentBasedModel.getLocalParentSampled(), spiHelper, closeables)); + } + if (parentBasedModel.getLocalParentNotSampled() != null) { + builder.setLocalParentNotSampled( + create(parentBasedModel.getLocalParentNotSampled(), spiHelper, closeables)); + } + return builder.build(); + } + + JaegerRemote jaegerRemoteModel = model.getJaegerRemote(); + if (jaegerRemoteModel != null) { + // Translate from file configuration scheme to environment variable scheme. This is ultimately + // interpreted by JaegerRemoteSamplerProvider, but we want to avoid the dependency on + // opentelemetry-sdk-extension-jaeger-remote-sampler + Map properties = new HashMap<>(); + if (jaegerRemoteModel.getEndpoint() != null) { + properties.put("endpoint", jaegerRemoteModel.getEndpoint()); + } + if (jaegerRemoteModel.getInterval() != null) { + properties.put("pollingInterval", String.valueOf(jaegerRemoteModel.getInterval())); + } + // TODO(jack-berg): determine how to support initial sampler. This is first case where a + // component configured via SPI has property that isn't available in the environment variable + // scheme. + String otelTraceSamplerArg = + properties.entrySet().stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(joining(",")); + + // TODO(jack-berg): add method for creating from map + ConfigProperties configProperties = + DefaultConfigProperties.createForTest( + Collections.singletonMap("otel.traces.sampler.arg", otelTraceSamplerArg)); + + return FileConfigUtil.addAndReturn( + closeables, + FileConfigUtil.assertNotNull( + samplerSpiManager(configProperties, spiHelper).getByName("jaeger_remote"), + "jaeger remote sampler")); + } + + // TODO(jack-berg): add support for generic SPI samplers + if (!model.getAdditionalProperties().isEmpty()) { + throw new ConfigurationException( + "Unrecognized sampler(s): " + + model.getAdditionalProperties().keySet().stream().collect(joining(",", "[", "]"))); + } + + return Sampler.parentBased(Sampler.alwaysOn()); + } + + private static NamedSpiManager samplerSpiManager( + ConfigProperties config, SpiHelper spiHelper) { + return spiHelper.loadConfigurable( + ConfigurableSamplerProvider.class, + ConfigurableSamplerProvider::getName, + ConfigurableSamplerProvider::createSampler, + config); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java index ae75006fc3d..dadea6cee9d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanLimits; +import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.util.List; import javax.annotation.Nullable; @@ -38,6 +39,10 @@ public SdkTracerProviderBuilder create( SpanLimitsFactory.getInstance().create(model.getLimits(), spiHelper, closeables); builder.setSpanLimits(spanLimits); + Sampler sampler = + SamplerFactory.getInstance().create(model.getSampler(), spiHelper, closeables); + builder.setSampler(sampler); + List processors = model.getProcessors(); if (processors != null) { processors.forEach( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 62fdb55ad98..b7d0e9d988f 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; @@ -14,6 +15,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOn; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporter; @@ -22,6 +24,7 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProvider; @@ -120,6 +123,7 @@ void create_Configured() { .setMaxNumberOfAttributesPerEvent(5) .setMaxNumberOfAttributesPerLink(6) .build()) + .setSampler(alwaysOn()) .addSpanProcessor( io.opentelemetry.sdk.trace.export.BatchSpanProcessor.builder( OtlpGrpcSpanExporter.getDefault()) @@ -158,6 +162,7 @@ void create_Configured() { .withLinkCountLimit(4) .withEventAttributeCountLimit(5) .withLinkAttributeCountLimit(6)) + .withSampler(new Sampler().withAlwaysOn(new AlwaysOn())) .withProcessors( Collections.singletonList( new SpanProcessor() diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java new file mode 100644 index 00000000000..c5c45eb1ae6 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java @@ -0,0 +1,142 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.fileconfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.internal.testing.CleanupExtension; +import io.opentelemetry.internal.testing.slf4j.SuppressLogger; +import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOff; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOn; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.JaegerRemote; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ParentBased; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TraceIdRatioBased; +import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler; +import java.io.Closeable; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +// Suppress logs from JaegerRemoteSampler +@SuppressLogger( + loggerName = "io.opentelemetry.sdk.extension.trace.jaeger.sampler.OkHttpGrpcService") +class SamplerFactoryTest { + + @RegisterExtension CleanupExtension cleanup = new CleanupExtension(); + + private final SpiHelper spiHelper = SpiHelper.create(SamplerFactoryTest.class.getClassLoader()); + + @ParameterizedTest + @MethodSource("createArguments") + void create( + @Nullable Sampler model, io.opentelemetry.sdk.trace.samplers.Sampler expectedSampler) { + // Some samplers like JaegerRemoteSampler are Closeable - ensure these get cleaned up + if (expectedSampler instanceof Closeable) { + cleanup.addCloseable((Closeable) expectedSampler); + } + + List closeables = new ArrayList<>(); + io.opentelemetry.sdk.trace.samplers.Sampler sampler = + SamplerFactory.getInstance().create(model, spiHelper, closeables); + cleanup.addCloseables(closeables); + + assertThat(sampler.toString()).isEqualTo(expectedSampler.toString()); + } + + private static Stream createArguments() { + return Stream.of( + Arguments.of( + null, + io.opentelemetry.sdk.trace.samplers.Sampler.parentBased( + io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn())), + Arguments.of( + new Sampler().withAlwaysOn(new AlwaysOn()), + io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn()), + Arguments.of( + new Sampler().withAlwaysOff(new AlwaysOff()), + io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOff()), + Arguments.of( + new Sampler().withTraceIdRatioBased(new TraceIdRatioBased()), + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(1.0d)), + Arguments.of( + new Sampler().withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.5d)), + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.5)), + Arguments.of( + new Sampler().withParentBased(new ParentBased()), + io.opentelemetry.sdk.trace.samplers.Sampler.parentBased( + io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn())), + Arguments.of( + new Sampler() + .withParentBased( + new ParentBased() + .withRoot( + new Sampler() + .withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.1d))) + .withRemoteParentSampled( + new Sampler() + .withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.2d))) + .withRemoteParentNotSampled( + new Sampler() + .withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.3d))) + .withLocalParentSampled( + new Sampler() + .withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.4d))) + .withLocalParentNotSampled( + new Sampler() + .withTraceIdRatioBased(new TraceIdRatioBased().withRatio(0.5d)))), + io.opentelemetry.sdk.trace.samplers.Sampler.parentBasedBuilder( + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.1d)) + .setRemoteParentSampled( + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.2d)) + .setRemoteParentNotSampled( + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.3d)) + .setLocalParentSampled( + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.4d)) + .setLocalParentNotSampled( + io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.5d)) + .build()), + Arguments.of( + new Sampler() + .withJaegerRemote( + new JaegerRemote() + .withEndpoint("http://jaeger-remote-endpoint") + .withInterval(10_000) + .withInitialSampler(new Sampler().withAlwaysOff(new AlwaysOff()))), + JaegerRemoteSampler.builder() + .setEndpoint("http://jaeger-remote-endpoint") + .setPollingInterval(Duration.ofSeconds(10)) + .build())); + } + + @Test + void create_SpiExporter() { + List closeables = new ArrayList<>(); + + assertThatThrownBy( + () -> + SamplerFactory.getInstance() + .create( + new Sampler() + .withAdditionalProperty("test", ImmutableMap.of("key1", "value1")), + spiHelper, + new ArrayList<>())) + .isInstanceOf(ConfigurationException.class) + .hasMessage("Unrecognized sampler(s): [test]"); + cleanup.addCloseables(closeables); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java index 99d8670f4f1..13be211a56e 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java @@ -6,12 +6,15 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOn; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Otlp; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Sampler; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporter; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessor; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProvider; @@ -75,6 +78,7 @@ void create_Configured() { .setMaxNumberOfAttributesPerEvent(5) .setMaxNumberOfAttributesPerLink(6) .build()) + .setSampler(alwaysOn()) .addSpanProcessor( io.opentelemetry.sdk.trace.export.BatchSpanProcessor.builder( OtlpGrpcSpanExporter.getDefault()) @@ -95,6 +99,7 @@ void create_Configured() { .withLinkCountLimit(4) .withEventAttributeCountLimit(5) .withLinkAttributeCountLimit(6)) + .withSampler(new Sampler().withAlwaysOn(new AlwaysOn())) .withProcessors( Collections.singletonList( new SpanProcessor()