diff --git a/sdk/all/build.gradle.kts b/sdk/all/build.gradle.kts index 313726e1723..86875e15a8e 100644 --- a/sdk/all/build.gradle.kts +++ b/sdk/all/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { testAnnotationProcessor("com.google.auto.value:auto-value") + testImplementation("org.apache.groovy:groovy:4.0.25") testImplementation(project(":sdk:testing")) jmh(project(":sdk:testing")) diff --git a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdkBuilder.java b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdkBuilder.java index c7168599554..338af797824 100644 --- a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdkBuilder.java +++ b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdkBuilder.java @@ -10,12 +10,13 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.internal.OpenTelemetrySdkBuilderUtil; -import io.opentelemetry.sdk.internal.SdkConfigProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import javax.annotation.Nullable; /** A builder for configuring an {@link OpenTelemetrySdk}. */ @@ -28,16 +29,28 @@ public final class OpenTelemetrySdkBuilder { @Nullable private Object configProvider; private static final boolean INCUBATOR_AVAILABLE; + @Nullable private static final Method CREATE_EXTENDED_OPEN_TELEMETRY_SDK_METHOD; static { boolean incubatorAvailable = false; + Method createExtendedOpenTelemetrySdk = null; try { Class.forName("io.opentelemetry.api.incubator.ExtendedOpenTelemetry"); + createExtendedOpenTelemetrySdk = + Class.forName("io.opentelemetry.sdk.IncubatingUtil") + .getDeclaredMethod( + "createExtendedOpenTelemetrySdk", OpenTelemetrySdk.class, Object.class); incubatorAvailable = true; } catch (ClassNotFoundException e) { // Not available + } catch (NoSuchMethodException e) { + throw new IllegalStateException( + "IncubatingUtil.createExtendedOpenTelemetrySdk could not be found." + + " This is a bug in OpenTelemetry.", + e); } INCUBATOR_AVAILABLE = incubatorAvailable; + CREATE_EXTENDED_OPEN_TELEMETRY_SDK_METHOD = createExtendedOpenTelemetrySdk; } /** @@ -89,12 +102,16 @@ public OpenTelemetrySdkBuilder setPropagators(ContextPropagators propagators) { } /** - * Sets the {@link SdkConfigProvider} to use. + * Sets the SDK config provider to use. * *
This method is experimental so not public. You may reflectively call it using {@link - * OpenTelemetrySdkBuilderUtil#setConfigProvider(OpenTelemetrySdkBuilder, SdkConfigProvider)}. + * OpenTelemetrySdkBuilderUtil#setConfigProvider(OpenTelemetrySdkBuilder, + * io.opentelemetry.sdk.internal.SdkConfigProvider)}. + * + *
The parameter type is {@link Object} to avoid introducing another direct incubator-linked + * method signature in the path Groovy eagerly inspects. */ - OpenTelemetrySdkBuilder setConfigProvider(SdkConfigProvider configProvider) { + OpenTelemetrySdkBuilder setConfigProvider(Object configProvider) { this.configProvider = requireNonNull(configProvider); return this; } @@ -143,7 +160,47 @@ public OpenTelemetrySdk build() { OpenTelemetrySdk openTelemetrySdk = new OpenTelemetrySdk(tracerProvider, meterProvider, loggerProvider, propagators); return INCUBATOR_AVAILABLE - ? IncubatingUtil.createExtendedOpenTelemetrySdk(openTelemetrySdk, configProvider) + ? createExtendedOpenTelemetrySdk(openTelemetrySdk, configProvider) : openTelemetrySdk; } + + private static OpenTelemetrySdk createExtendedOpenTelemetrySdk( + OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) { + return createExtendedOpenTelemetrySdk( + openTelemetrySdk, configProvider, requireNonNull(CREATE_EXTENDED_OPEN_TELEMETRY_SDK_METHOD)); + } + + static OpenTelemetrySdk createExtendedOpenTelemetrySdk( + OpenTelemetrySdk openTelemetrySdk, + @Nullable Object configProvider, + Method createExtendedOpenTelemetrySdkMethod) { + try { + return (OpenTelemetrySdk) + createExtendedOpenTelemetrySdkMethod.invoke(null, openTelemetrySdk, configProvider); + } catch (IllegalAccessException e) { + throw new IllegalStateException( + "IncubatingUtil.createExtendedOpenTelemetrySdk could not be invoked." + + " This is a bug in OpenTelemetry.", + e); + } catch (IllegalArgumentException e) { + throw new IllegalStateException( + "IncubatingUtil.createExtendedOpenTelemetrySdk could not be called with the expected" + + " arguments. This is a bug in OpenTelemetry.", + e); + } catch (InvocationTargetException e) { + Throwable cause = e.getTargetException(); + // Preserve the original application behavior rather than wrapping runtime failures emitted + // by the incubator path in a reflective InvocationTargetException. + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw new IllegalStateException( + "IncubatingUtil.createExtendedOpenTelemetrySdk failed." + + " This is a bug in OpenTelemetry.", + cause); + } + } } diff --git a/sdk/all/src/main/java/io/opentelemetry/sdk/internal/OpenTelemetrySdkBuilderUtil.java b/sdk/all/src/main/java/io/opentelemetry/sdk/internal/OpenTelemetrySdkBuilderUtil.java index ff017d64fc0..f34b645eea7 100644 --- a/sdk/all/src/main/java/io/opentelemetry/sdk/internal/OpenTelemetrySdkBuilderUtil.java +++ b/sdk/all/src/main/java/io/opentelemetry/sdk/internal/OpenTelemetrySdkBuilderUtil.java @@ -27,8 +27,7 @@ public static OpenTelemetrySdkBuilder setConfigProvider( OpenTelemetrySdkBuilder builder, SdkConfigProvider configProvider) { try { Method method = - OpenTelemetrySdkBuilder.class.getDeclaredMethod( - "setConfigProvider", SdkConfigProvider.class); + OpenTelemetrySdkBuilder.class.getDeclaredMethod("setConfigProvider", Object.class); method.setAccessible(true); method.invoke(builder, configProvider); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { diff --git a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java index c6a48a0dbad..35f07e2e71f 100644 --- a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java +++ b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import groovy.lang.GroovyClassLoader; import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; @@ -39,8 +40,10 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.lang.reflect.Method; import java.time.Duration; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -138,6 +141,126 @@ void builder() { assertThat(openTelemetry.getPropagators()).isEqualTo(propagators); } + @Test + void buildFromGroovyWithoutIncubator() throws Exception { + try (GroovyClassLoader groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader())) { + Class> groovyClass = + groovyClassLoader.parseClass( + "import io.opentelemetry.sdk.OpenTelemetrySdk\n" + + "class GroovyBuilderCaller {\n" + + " static Object buildSdk() {\n" + + " OpenTelemetrySdk.builder().build()\n" + + " }\n" + + "}\n"); + + assertThat(groovyClass.getMethod("buildSdk").invoke(null)) + .isInstanceOf(OpenTelemetrySdk.class); + } + } + + @Test + void createExtendedOpenTelemetrySdk_rethrowsRuntimeException() throws Exception { + Method method = + OpenTelemetrySdkTest.class.getDeclaredMethod( + "throwRuntimeException", OpenTelemetrySdk.class, Object.class); + + assertThatThrownBy( + () -> + OpenTelemetrySdkBuilder.createExtendedOpenTelemetrySdk( + OpenTelemetrySdk.builder().build(), null, method)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("runtime boom"); + } + + @Test + void createExtendedOpenTelemetrySdk_rethrowsError() throws Exception { + Method method = + OpenTelemetrySdkTest.class.getDeclaredMethod( + "throwError", OpenTelemetrySdk.class, Object.class); + + assertThatThrownBy( + () -> + OpenTelemetrySdkBuilder.createExtendedOpenTelemetrySdk( + OpenTelemetrySdk.builder().build(), null, method)) + .isInstanceOf(AssertionError.class) + .hasMessage("error boom"); + } + + @Test + void createExtendedOpenTelemetrySdk_wrapsCheckedException() throws Exception { + Method method = + OpenTelemetrySdkTest.class.getDeclaredMethod( + "throwCheckedException", OpenTelemetrySdk.class, Object.class); + + assertThatThrownBy( + () -> + OpenTelemetrySdkBuilder.createExtendedOpenTelemetrySdk( + OpenTelemetrySdk.builder().build(), null, method)) + .isInstanceOf(IllegalStateException.class) + .hasMessage( + "IncubatingUtil.createExtendedOpenTelemetrySdk failed. This is a bug in OpenTelemetry.") + .cause() + .isInstanceOf(Exception.class) + .hasMessage("checked boom"); + } + + @Test + void createExtendedOpenTelemetrySdk_wrapsIllegalArgumentException() throws Exception { + Method method = OpenTelemetrySdkTest.class.getDeclaredMethod("wrongSignature", String.class); + + assertThatThrownBy( + () -> + OpenTelemetrySdkBuilder.createExtendedOpenTelemetrySdk( + OpenTelemetrySdk.builder().build(), null, method)) + .isInstanceOf(IllegalStateException.class) + .hasMessage( + "IncubatingUtil.createExtendedOpenTelemetrySdk could not be called with the expected" + + " arguments. This is a bug in OpenTelemetry.") + .cause() + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void createExtendedOpenTelemetrySdk_wrapsIllegalAccessException() throws Exception { + Method method = + OpenTelemetrySdkTest.class.getDeclaredMethod( + "privateNoAccess", OpenTelemetrySdk.class, Object.class); + + assertThatThrownBy( + () -> + OpenTelemetrySdkBuilder.createExtendedOpenTelemetrySdk( + OpenTelemetrySdk.builder().build(), null, method)) + .isInstanceOf(IllegalStateException.class) + .hasMessage( + "IncubatingUtil.createExtendedOpenTelemetrySdk could not be invoked. This is a bug in OpenTelemetry.") + .cause() + .isInstanceOf(IllegalAccessException.class); + } + + static OpenTelemetrySdk throwRuntimeException( + OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) { + throw new IllegalStateException("runtime boom"); + } + + static OpenTelemetrySdk throwError( + OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) { + throw new AssertionError("error boom"); + } + + static OpenTelemetrySdk throwCheckedException( + OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) throws Exception { + throw new Exception("checked boom"); + } + + static OpenTelemetrySdk wrongSignature(String ignored) { + return null; + } + + private static OpenTelemetrySdk privateNoAccess( + OpenTelemetrySdk openTelemetrySdk, @Nullable Object configProvider) { + return openTelemetrySdk; + } + @Test void getTracer() { assertThat(GlobalOpenTelemetry.getTracer("testTracer1"))