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 cca5f98dc89..09cec8b892d 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 @@ -443,65 +443,19 @@ public AutoConfiguredOpenTelemetrySdk build() { List closeables = new ArrayList<>(); try { - OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder().build(); - boolean sdkEnabled = !config.getBoolean("otel.sdk.disabled", false); + OpenTelemetrySdkBuilder sdkBuilder = OpenTelemetrySdk.builder(); + + // The propagation system is part of the API and functions in the absence of an SDK. + ContextPropagators propagators = + PropagatorConfiguration.configurePropagators(config, spiHelper, propagatorCustomizer); + sdkBuilder.setPropagators(propagators); + boolean sdkEnabled = !config.getBoolean("otel.sdk.disabled", false); if (sdkEnabled) { - SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); - meterProviderBuilder.setResource(resource); - MeterProviderConfiguration.configureMeterProvider( - meterProviderBuilder, - config, - spiHelper, - metricReaderCustomizer, - metricExporterCustomizer, - closeables); - meterProviderBuilder = meterProviderCustomizer.apply(meterProviderBuilder, config); - SdkMeterProvider meterProvider = meterProviderBuilder.build(); - closeables.add(meterProvider); - - SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); - tracerProviderBuilder.setResource(resource); - TracerProviderConfiguration.configureTracerProvider( - tracerProviderBuilder, - config, - spiHelper, - meterProvider, - spanExporterCustomizer, - spanProcessorCustomizer, - samplerCustomizer, - closeables); - tracerProviderBuilder = tracerProviderCustomizer.apply(tracerProviderBuilder, config); - SdkTracerProvider tracerProvider = tracerProviderBuilder.build(); - closeables.add(tracerProvider); - - SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); - loggerProviderBuilder.setResource(resource); - LoggerProviderConfiguration.configureLoggerProvider( - loggerProviderBuilder, - config, - spiHelper, - meterProvider, - logRecordExporterCustomizer, - logRecordProcessorCustomizer, - closeables); - loggerProviderBuilder = loggerProviderCustomizer.apply(loggerProviderBuilder, config); - SdkLoggerProvider loggerProvider = loggerProviderBuilder.build(); - closeables.add(loggerProvider); - - ContextPropagators propagators = - PropagatorConfiguration.configurePropagators(config, spiHelper, propagatorCustomizer); - - OpenTelemetrySdkBuilder sdkBuilder = - OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .setLoggerProvider(loggerProvider) - .setMeterProvider(meterProvider) - .setPropagators(propagators); - - openTelemetrySdk = sdkBuilder.build(); + configureSdk(sdkBuilder, config, resource, spiHelper, closeables); } + OpenTelemetrySdk openTelemetrySdk = sdkBuilder.build(); maybeRegisterShutdownHook(openTelemetrySdk); maybeSetAsGlobal(openTelemetrySdk); callAutoConfigureListeners(spiHelper, openTelemetrySdk); @@ -526,6 +480,62 @@ public AutoConfiguredOpenTelemetrySdk build() { } } + // Visible for testing + void configureSdk( + OpenTelemetrySdkBuilder sdkBuilder, + ConfigProperties config, + Resource resource, + SpiHelper spiHelper, + List closeables) { + SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); + meterProviderBuilder.setResource(resource); + + MeterProviderConfiguration.configureMeterProvider( + meterProviderBuilder, + config, + spiHelper, + metricReaderCustomizer, + metricExporterCustomizer, + closeables); + meterProviderBuilder = meterProviderCustomizer.apply(meterProviderBuilder, config); + SdkMeterProvider meterProvider = meterProviderBuilder.build(); + closeables.add(meterProvider); + + SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); + tracerProviderBuilder.setResource(resource); + TracerProviderConfiguration.configureTracerProvider( + tracerProviderBuilder, + config, + spiHelper, + meterProvider, + spanExporterCustomizer, + spanProcessorCustomizer, + samplerCustomizer, + closeables); + tracerProviderBuilder = tracerProviderCustomizer.apply(tracerProviderBuilder, config); + SdkTracerProvider tracerProvider = tracerProviderBuilder.build(); + closeables.add(tracerProvider); + + SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); + loggerProviderBuilder.setResource(resource); + LoggerProviderConfiguration.configureLoggerProvider( + loggerProviderBuilder, + config, + spiHelper, + meterProvider, + logRecordExporterCustomizer, + logRecordProcessorCustomizer, + closeables); + loggerProviderBuilder = loggerProviderCustomizer.apply(loggerProviderBuilder, config); + SdkLoggerProvider loggerProvider = loggerProviderBuilder.build(); + closeables.add(loggerProvider); + + sdkBuilder + .setTracerProvider(tracerProvider) + .setLoggerProvider(loggerProvider) + .setMeterProvider(meterProvider); + } + @Nullable private static AutoConfiguredOpenTelemetrySdk maybeConfigureFromFile( ConfigProperties config, ComponentLoader componentLoader) { @@ -607,7 +617,7 @@ void callAutoConfigureListeners(SpiHelper spiHelper, OpenTelemetrySdk openTeleme } @SuppressWarnings("deprecation") // Support deprecated SdkTracerProviderConfigurer - private void mergeSdkTracerProviderConfigurer() { + void mergeSdkTracerProviderConfigurer() { for (io.opentelemetry.sdk.autoconfigure.spi.traces.SdkTracerProviderConfigurer configurer : componentLoader.load( io.opentelemetry.sdk.autoconfigure.spi.traces.SdkTracerProviderConfigurer.class)) { 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 ed2c94a7108..9b72fbefb82 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 @@ -67,7 +67,9 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Supplier; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -391,7 +393,15 @@ void builder_registersShutdownHook() { } @Test - void shutdownHook() throws InterruptedException { + void builder_customizes() { + builder = spy(builder); + OpenTelemetrySdk sdk = builder.build().getOpenTelemetrySdk(); + assertThat(sdk).isNotNull(); + verify(builder, times(1)).mergeSdkTracerProviderConfigurer(); + } + + @Test + void builder_shutdownHook() throws InterruptedException { OpenTelemetrySdk sdk = mock(OpenTelemetrySdk.class); Thread thread = builder.shutdownHook(sdk); @@ -411,7 +421,7 @@ void builder_CallAutoConfigureListeners() { } @Test - void callAutoConfigureListeners() { + void builder_callAutoConfigureListeners() { AutoConfigureListener listener = mock(AutoConfigureListener.class); SpiHelper spiHelper = mock(SpiHelper.class); when(spiHelper.getListeners()).thenReturn(Collections.singleton(listener)); @@ -455,6 +465,39 @@ void disableSdk() { verify(logCustomizer, never()).apply(any(), any()); } + @Test + void disableSdk_PropagatorCustomizer() { + Context extracted = Context.root().with(ContextKey.named("animal"), "bear"); + + when(propagator2.extract(any(), any(), any())).thenReturn(extracted); + + AutoConfiguredOpenTelemetrySdk autoConfiguredSdk = + AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "true")) + .addPropertiesSupplier(() -> singletonMap("otel.propagators", "tracecontext")) + .addPropagatorCustomizer( + (previous, config) -> { + assertThat(previous).isSameAs(W3CTraceContextPropagator.getInstance()); + return propagator1; + }) + .addPropagatorCustomizer( + (previous, config) -> { + assertThat(previous).isSameAs(propagator1); + return propagator2; + }) + .build(); + + // When the SDK is disabled, propagators are still configured + assertThat(autoConfiguredSdk.getOpenTelemetrySdk().getPropagators()).isNotNull(); + Consumer propagatorConsumer = + propagator -> { + assertThat(propagator.extract(Context.root(), Collections.emptyMap(), getter)) + .isEqualTo(extracted); + }; + assertThat(autoConfiguredSdk.getOpenTelemetrySdk().getPropagators().getTextMapPropagator()) + .isInstanceOfSatisfying(TextMapPropagator.class, propagatorConsumer); + } + @Test void tracerProviderCustomizer() { InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); @@ -510,6 +553,88 @@ void testNonStringProperties() { }); } + @Test + @SuppressLogger(AutoConfiguredOpenTelemetrySdkBuilder.class) + void configurationError_propagators() { + BiFunction + traceCustomizer = getTracerProviderBuilderSpy(); + BiFunction + metricCustomizer = getMeterProviderBuilderSpy(); + BiFunction logCustomizer = + getLoggerProviderBuilderSpy(); + + assertThatThrownBy( + () -> + // Override the provider builders with mocks which we can verify are closed + AutoConfiguredOpenTelemetrySdk.builder() + .addTracerProviderCustomizer(traceCustomizer) + .addMeterProviderCustomizer(metricCustomizer) + .addLoggerProviderCustomizer(logCustomizer) + .addPropertiesSupplier(() -> singletonMap("otel.metrics.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.traces.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.logs.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.propagators", "foo")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "true")) + .build()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unrecognized value for otel.propagators"); + + // When the SDK is disabled and propagators are mis-configured, none of the customizers are + // called + verify(traceCustomizer, never()).apply(any(), any()); + verify(metricCustomizer, never()).apply(any(), any()); + verify(logCustomizer, never()).apply(any(), any()); + + assertThatThrownBy( + () -> + // Override the provider builders with mocks which we can verify are closed + AutoConfiguredOpenTelemetrySdk.builder() + .addTracerProviderCustomizer(traceCustomizer) + .addMeterProviderCustomizer(metricCustomizer) + .addLoggerProviderCustomizer(logCustomizer) + .addPropertiesSupplier(() -> singletonMap("otel.metrics.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.traces.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.logs.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.propagators", "foo")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "false")) + .build()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unrecognized value for otel.propagators"); + + // When the SDK is enabled and propagators are mis-configured, none of the customizers are + // called + verify(traceCustomizer, never()).apply(any(), any()); + verify(metricCustomizer, never()).apply(any(), any()); + verify(logCustomizer, never()).apply(any(), any()); + } + + @Test + @SuppressLogger(AutoConfiguredOpenTelemetrySdkBuilder.class) + void configurationError_runtime() { + BiFunction + traceCustomizer = getTracerProviderBuilderSpy(); + BiFunction + metricCustomizer = getMeterProviderBuilderSpy(); + BiFunction logCustomizer = + getLoggerProviderBuilderSpy(); + + doThrow(new RuntimeException()).when(traceCustomizer).apply(any(), any()); + + assertThatThrownBy( + () -> + AutoConfiguredOpenTelemetrySdk.builder() + .addTracerProviderCustomizer(traceCustomizer) + .addMeterProviderCustomizer(metricCustomizer) + .addLoggerProviderCustomizer(logCustomizer) + .addPropertiesSupplier(() -> singletonMap("otel.metrics.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.traces.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.logs.exporter", "none")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "false")) + .build()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Unexpected configuration error"); + } + @Test @SuppressLogger(AutoConfiguredOpenTelemetrySdkBuilder.class) void configurationError_ClosesResources() { @@ -539,16 +664,37 @@ void configurationError_ClosesResources() { .addLoggerProviderCustomizer((u1, u2) -> loggerProviderBuilder) .addPropertiesSupplier(() -> singletonMap("otel.metrics.exporter", "none")) .addPropertiesSupplier(() -> singletonMap("otel.traces.exporter", "none")) - .addPropertiesSupplier(() -> singletonMap("otel.logs.exporter", "none")) - .addPropertiesSupplier(() -> singletonMap("otel.propagators", "foo")) + .addPropertiesSupplier(() -> singletonMap("otel.logs.exporter", "foo")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "false")) .build()) .isInstanceOf(ConfigurationException.class) - .hasMessageContaining("Unrecognized value for otel.propagators"); + .hasMessageContaining("Unrecognized value for otel.logs.exporter: foo"); verify(tracerProvider).close(); verify(meterProvider).close(); - verify(loggerProvider).close(); logs.assertContains("Error closing io.opentelemetry.sdk.trace.SdkTracerProvider: Error!"); } + + @Test + @SuppressLogger(AutoConfiguredOpenTelemetrySdkBuilder.class) + void configurationError_fileNotFound() { + assertThatThrownBy( + () -> + AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(() -> singletonMap("otel.config.file", "foo")) + .addPropertiesSupplier( + () -> singletonMap("otel.experimental.config.file", "foo")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "true")) + .build()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Configuration file not found"); + + Assertions.assertDoesNotThrow( + () -> + AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(() -> singletonMap("otel.experimental.config.file", "")) + .addPropertiesSupplier(() -> singletonMap("otel.sdk.disabled", "true")) + .build()); + } }