diff --git a/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts index 2aa0a0f5a69..3c420e1719a 100644 --- a/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts @@ -154,6 +154,8 @@ if (!project.hasProperty("otel.release") && !project.name.startsWith("bom")) { // Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130 // with some changes. val exclusions = mutableListOf() + // Generics are not detected correctly + exclusions.add("CLASS_GENERIC_TEMPLATE_CHANGED") // Allow new default methods on interfaces exclusions.add("METHOD_NEW_DEFAULT") // Allow adding default implementations for default methods diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java index 50604716f79..fe9d75adae3 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java @@ -13,6 +13,7 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; @@ -102,6 +103,9 @@ public final class AutoConfiguredOpenTelemetrySdkBuilder implements AutoConfigur private final List>> propertiesCustomizers = new ArrayList<>(); + private Function configPropertiesCustomizer = + Function.identity(); + private SpiHelper spiHelper = SpiHelper.create(AutoConfiguredOpenTelemetrySdk.class.getClassLoader()); @@ -253,6 +257,21 @@ public AutoConfiguredOpenTelemetrySdkBuilder addPropertiesCustomizer( return this; } + /** + * Adds a {@link Function} to invoke the with the {@link ConfigProperties} to allow customization. + * + *

The argument to the function is the {@link ConfigProperties}, with the {@link + * #addPropertiesCustomizer(Function)} already applied. + * + *

The return value of the {@link Function} replace the {@link ConfigProperties} to be used. + */ + AutoConfiguredOpenTelemetrySdkBuilder setConfigPropertiesCustomizer( + Function configPropertiesCustomizer) { + requireNonNull(configPropertiesCustomizer, "configPropertiesCustomizer"); + this.configPropertiesCustomizer = configPropertiesCustomizer; + return this; + } + /** * Adds a {@link BiFunction} to invoke the with the {@link SdkMeterProviderBuilder} to allow * customization. The return value of the {@link BiFunction} will replace the passed-in argument. @@ -385,6 +404,13 @@ public AutoConfiguredOpenTelemetrySdkBuilder setServiceClassLoader( return this; } + /** Sets the {@link ComponentLoader} to be used to load SPI implementations. */ + AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader(ComponentLoader componentLoader) { + requireNonNull(componentLoader, "componentLoader"); + this.spiHelper = SpiHelper.create(componentLoader); + return this; + } + /** * Returns a new {@link AutoConfiguredOpenTelemetrySdk} holding components auto-configured using * the settings of this {@link AutoConfiguredOpenTelemetrySdkBuilder}. @@ -590,7 +616,7 @@ private ConfigProperties computeConfigProperties() { Map overrides = customizer.apply(properties); properties = properties.withOverrides(overrides); } - return properties; + return configPropertiesCustomizer.apply(properties); } // Visible for testing diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java index 1241acccc8d..b2e2a3858c8 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/TracerProviderConfiguration.java @@ -173,21 +173,13 @@ static Sampler configureSampler(String sampler, ConfigProperties config, SpiHelp case "always_off": return Sampler.alwaysOff(); case "traceidratio": - { - double ratio = - config.getDouble("otel.traces.sampler.arg", DEFAULT_TRACEIDRATIO_SAMPLE_RATIO); - return Sampler.traceIdRatioBased(ratio); - } + return ratioSampler(config); case PARENTBASED_ALWAYS_ON: return Sampler.parentBased(Sampler.alwaysOn()); case "parentbased_always_off": return Sampler.parentBased(Sampler.alwaysOff()); case "parentbased_traceidratio": - { - double ratio = - config.getDouble("otel.traces.sampler.arg", DEFAULT_TRACEIDRATIO_SAMPLE_RATIO); - return Sampler.parentBased(Sampler.traceIdRatioBased(ratio)); - } + return Sampler.parentBased(ratioSampler(config)); case "parentbased_jaeger_remote": Sampler jaegerRemote = spiSamplersManager.getByName("jaeger_remote"); if (jaegerRemote == null) { @@ -205,5 +197,10 @@ static Sampler configureSampler(String sampler, ConfigProperties config, SpiHelp } } + private static Sampler ratioSampler(ConfigProperties config) { + double ratio = config.getDouble("otel.traces.sampler.arg", DEFAULT_TRACEIDRATIO_SAMPLE_RATIO); + return Sampler.traceIdRatioBased(ratio); + } + private TracerProviderConfiguration() {} } diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/AutoConfigureUtil.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/AutoConfigureUtil.java index e77620fc49a..8a50d782031 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/AutoConfigureUtil.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/AutoConfigureUtil.java @@ -6,9 +6,11 @@ package io.opentelemetry.sdk.autoconfigure.internal; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.function.Function; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at @@ -30,4 +32,38 @@ public static ConfigProperties getConfig( "Error calling getConfig on AutoConfiguredOpenTelemetrySdk", e); } } + + /** Sets the {@link ComponentLoader} to be used in the auto-configuration process. */ + public static AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader( + AutoConfiguredOpenTelemetrySdkBuilder builder, ComponentLoader componentLoader) { + try { + Method method = + AutoConfiguredOpenTelemetrySdkBuilder.class.getDeclaredMethod( + "setComponentLoader", ComponentLoader.class); + method.setAccessible(true); + method.invoke(builder, componentLoader); + return builder; + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException( + "Error calling setComponentLoader on AutoConfiguredOpenTelemetrySdkBuilder", e); + } + } + + /** Sets the {@link ConfigProperties} customizer to be used in the auto-configuration process. */ + public static AutoConfiguredOpenTelemetrySdkBuilder setConfigPropertiesCustomizer( + AutoConfiguredOpenTelemetrySdkBuilder builder, + Function customizer) { + try { + Method method = + AutoConfiguredOpenTelemetrySdkBuilder.class.getDeclaredMethod( + "setConfigPropertiesCustomizer", Function.class); + method.setAccessible(true); + method.invoke(builder, customizer); + return builder; + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException( + "Error calling setConfigPropertiesCustomizer on AutoConfiguredOpenTelemetrySdkBuilder", + e); + } + } } diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ComponentLoader.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ComponentLoader.java new file mode 100644 index 00000000000..49bfc4e52f2 --- /dev/null +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/ComponentLoader.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure.internal; + +/** + * A loader for components that are discovered via SPI. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface ComponentLoader { + /** + * Load implementations of an SPI. + * + * @param spiClass the SPI class + * @param the SPI type + * @return iterable of SPI implementations + */ + Iterable load(Class spiClass); +} diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java index 4002ce65329..d54e526d44a 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelper.java @@ -27,20 +27,22 @@ */ public final class SpiHelper { - private final ClassLoader classLoader; - private final SpiFinder spiFinder; + private final ComponentLoader componentLoader; private final Set listeners = Collections.newSetFromMap(new IdentityHashMap<>()); - // Visible for testing - SpiHelper(ClassLoader classLoader, SpiFinder spiFinder) { - this.classLoader = classLoader; - this.spiFinder = spiFinder; + private SpiHelper(ComponentLoader componentLoader) { + this.componentLoader = componentLoader; } /** Create a {@link SpiHelper} which loads SPIs using the {@code classLoader}. */ public static SpiHelper create(ClassLoader classLoader) { - return new SpiHelper(classLoader, ServiceLoader::load); + return new SpiHelper(new ServiceLoaderComponentLoader(classLoader)); + } + + /** Create a {@link SpiHelper} which loads SPIs using the {@code componentLoader}. */ + public static SpiHelper create(ComponentLoader componentLoader) { + return new SpiHelper(componentLoader); } /** @@ -96,7 +98,7 @@ public List loadOrdered(Class spiClass) { */ public List load(Class spiClass) { List result = new ArrayList<>(); - for (T service : spiFinder.load(spiClass, classLoader)) { + for (T service : componentLoader.load(spiClass)) { maybeAddListener(service); result.add(service); } @@ -114,8 +116,16 @@ public Set getListeners() { return Collections.unmodifiableSet(listeners); } - // Visible for testing - interface SpiFinder { - Iterable load(Class spiClass, ClassLoader classLoader); + private static class ServiceLoaderComponentLoader implements ComponentLoader { + private final ClassLoader classLoader; + + private ServiceLoaderComponentLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Iterable load(Class spiClass) { + return ServiceLoader.load(spiClass, classLoader); + } } } diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java index 01047907fca..5e8ea677c7f 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import io.github.netmikey.logunit.api.LogCapturer; @@ -36,10 +37,14 @@ import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.internal.testing.slf4j.SuppressLogger; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; +import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; +import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.LogRecordProcessor; import io.opentelemetry.sdk.logs.SdkLoggerProvider; @@ -315,6 +320,32 @@ void builder_addSpanProcessorCustomizer() { verifyNoInteractions(spanExporter1); } + @Test + void builder_addAutoConfigurationCustomizerProviderUsingComponentLoader() { + AutoConfigurationCustomizerProvider customizerProvider = + mock(AutoConfigurationCustomizerProvider.class); + + SpiHelper spiHelper = + SpiHelper.create(AutoConfiguredOpenTelemetrySdkBuilder.class.getClassLoader()); + + AutoConfigureUtil.setComponentLoader( + builder, + new ComponentLoader() { + @SuppressWarnings("unchecked") + @Override + public Iterable load(Class spiClass) { + if (spiClass.equals(AutoConfigurationCustomizerProvider.class)) { + return Collections.singletonList((T) customizerProvider); + } + return spiHelper.load(spiClass); + } + }) + .build(); + + verify(customizerProvider).customize(any()); + verifyNoMoreInteractions(customizerProvider); + } + @Test void builder_addPropertiesSupplier() { AutoConfiguredOpenTelemetrySdk autoConfigured = @@ -356,6 +387,23 @@ void builder_addPropertiesCustomizer() { assertThat(autoConfigured.getConfig().getString("some.key")).isEqualTo("override-2"); } + @Test + void builder_setConfigPropertiesCustomizer() { + AutoConfiguredOpenTelemetrySdk autoConfigured = + AutoConfigureUtil.setConfigPropertiesCustomizer( + builder.addPropertiesCustomizer(config -> singletonMap("some-key", "defaultValue")), + config -> { + assertThat(config.getString("some-key")).isEqualTo("defaultValue"); + + Map map = new HashMap<>(singletonMap("some-key", "override")); + map.putAll(disableExportPropertySupplier().get()); + return DefaultConfigProperties.createFromMap(map); + }) + .build(); + + assertThat(autoConfigured.getConfig().getString("some.key")).isEqualTo("override"); + } + @Test void builder_addMeterProviderCustomizer() { Mockito.lenient().when(metricReader.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelperTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelperTest.java index a2d5c870d4b..ad7a96704e6 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelperTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/internal/SpiHelperTest.java @@ -11,6 +11,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,11 +29,11 @@ public class SpiHelperTest { @Test public void canRetrieveByName() { - SpiHelper.SpiFinder mockFinder = mock(SpiHelper.SpiFinder.class); - when(mockFinder.load(any(), any())) + ComponentLoader mockLoader = spy(ComponentLoader.class); + when(mockLoader.load(any())) .thenReturn(Collections.singletonList(new SpiExampleProviderImplementation())); - SpiHelper spiHelper = new SpiHelper(SpiHelperTest.class.getClassLoader(), mockFinder); + SpiHelper spiHelper = SpiHelper.create(mockLoader); NamedSpiManager spiProvider = spiHelper.loadConfigurable( @@ -49,10 +50,10 @@ public void canRetrieveByName() { public void instantiatesImplementationsLazily() { SpiExampleProvider mockProvider = mock(SpiExampleProvider.class); when(mockProvider.getName()).thenReturn("lazy-init-example"); - SpiHelper.SpiFinder mockFinder = mock(SpiHelper.SpiFinder.class); - when(mockFinder.load(any(), any())).thenReturn(Collections.singletonList(mockProvider)); + ComponentLoader mockLoader = spy(ComponentLoader.class); + when(mockLoader.load(any())).thenReturn(Collections.singletonList(mockProvider)); - SpiHelper spiHelper = new SpiHelper(SpiHelperTest.class.getClassLoader(), mockFinder); + SpiHelper spiHelper = SpiHelper.create(mockLoader); NamedSpiManager spiProvider = spiHelper.loadConfigurable( @@ -68,11 +69,11 @@ public void instantiatesImplementationsLazily() { @Test public void onlyInstantiatesOnce() { - SpiHelper.SpiFinder mockFinder = mock(SpiHelper.SpiFinder.class); - when(mockFinder.load(any(), any())) + ComponentLoader mockLoader = mock(ComponentLoader.class); + when(mockLoader.load(any())) .thenReturn(Collections.singletonList(new SpiExampleProviderImplementation())); - SpiHelper spiHelper = new SpiHelper(SpiHelperTest.class.getClassLoader(), mockFinder); + SpiHelper spiHelper = SpiHelper.create(mockLoader); NamedSpiManager spiProvider = spiHelper.loadConfigurable( @@ -93,10 +94,10 @@ public void failureToInitializeThrows() { when(mockProvider.getName()).thenReturn("init-failure-example"); when(mockProvider.createSpiExample(any())).thenThrow(new RuntimeException()); - SpiHelper.SpiFinder mockFinder = mock(SpiHelper.SpiFinder.class); - when(mockFinder.load(any(), any())).thenReturn(Collections.singletonList(mockProvider)); + ComponentLoader mockLoader = spy(ComponentLoader.class); + when(mockLoader.load(any())).thenReturn(Collections.singletonList(mockProvider)); - SpiHelper spiHelper = new SpiHelper(SpiHelperTest.class.getClassLoader(), mockFinder); + SpiHelper spiHelper = SpiHelper.create(mockLoader); NamedSpiManager spiProvider = spiHelper.loadConfigurable( @@ -120,11 +121,10 @@ void loadsOrderedSpi() { when(spi2.order()).thenReturn(0); when(spi3.order()).thenReturn(1); - SpiHelper.SpiFinder mockFinder = mock(SpiHelper.SpiFinder.class); - when(mockFinder.load(ResourceProvider.class, SpiHelper.class.getClassLoader())) - .thenReturn(asList(spi1, spi2, spi3)); + ComponentLoader mockLoader = spy(ComponentLoader.class); + when(mockLoader.load(ResourceProvider.class)).thenReturn(asList(spi1, spi2, spi3)); - SpiHelper spiHelper = new SpiHelper(SpiHelperTest.class.getClassLoader(), mockFinder); + SpiHelper spiHelper = SpiHelper.create(mockLoader); List loadedSpi = spiHelper.loadOrdered(ResourceProvider.class);