From c1a31bb3890ab360389235adf69cbba39796d1f7 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 Nov 2024 09:10:05 +0100 Subject: [PATCH 1/2] port Spring Boot 3 otel changes to Spring Boot 2 --- sentry-spring-boot/build.gradle.kts | 6 ++ .../spring/boot/SentryAutoConfiguration.java | 38 +++++---- .../boot/SentryAutoConfigurationTest.kt | 82 ++++++++++++++----- .../boot/it/SentrySpringIntegrationTest.kt | 8 ++ sentry-spring/api/sentry-spring.api | 11 +++ sentry-spring/build.gradle.kts | 3 + ...etryAgentWithoutAutoInitConfiguration.java | 27 ++++++ ...ntryOpenTelemetryNoAgentConfiguration.java | 38 +++++++++ 8 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java create mode 100644 sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java diff --git a/sentry-spring-boot/build.gradle.kts b/sentry-spring-boot/build.gradle.kts index 548b02face5..f6a84d47f06 100644 --- a/sentry-spring-boot/build.gradle.kts +++ b/sentry-spring-boot/build.gradle.kts @@ -68,7 +68,13 @@ dependencies { testImplementation(Config.Libs.springBootStarterSecurity) testImplementation(Config.Libs.springBootStarterAop) testImplementation(Config.Libs.springBootStarterQuartz) + testImplementation(Config.Libs.OpenTelemetry.otelSdk) + testImplementation(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi) + testImplementation(Config.Libs.springBoot3StarterOpenTelemetry) testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) + testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgent) + testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization) + testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap) } configure { diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index e9420aafa36..7476b756dd6 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -4,6 +4,7 @@ import graphql.GraphQLError; import io.sentry.EventProcessor; import io.sentry.IScopes; +import io.sentry.ISpanFactory; import io.sentry.ITransportFactory; import io.sentry.InitPriority; import io.sentry.Integration; @@ -28,6 +29,8 @@ import io.sentry.spring.checkin.SentryQuartzConfiguration; import io.sentry.spring.exception.SentryCaptureExceptionParameterPointcutConfiguration; import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration; +import io.sentry.spring.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration; +import io.sentry.spring.opentelemetry.SentryOpenTelemetryNoAgentConfiguration; import io.sentry.spring.tracing.SentryAdviceConfiguration; import io.sentry.spring.tracing.SentrySpanPointcutConfiguration; import io.sentry.spring.tracing.SentryTracingFilter; @@ -115,10 +118,29 @@ static class HubConfiguration { return new InAppIncludesResolver(); } + @Configuration(proxyBeanMethods = false) + @Import(SentryOpenTelemetryAgentWithoutAutoInitConfiguration.class) + @Open + @ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false") + @ConditionalOnClass(name = {"io.sentry.opentelemetry.agent.AgentMarker"}) + static class OpenTelemetryAgentWithoutAutoInitConfiguration {} + + @Configuration(proxyBeanMethods = false) + @Import(SentryOpenTelemetryNoAgentConfiguration.class) + @Open + @ConditionalOnClass( + name = { + "io.opentelemetry.api.OpenTelemetry", + "io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider" + }) + @ConditionalOnMissingClass("io.sentry.opentelemetry.agent.AgentMarker") + static class OpenTelemetryNoAgentConfiguration {} + @Bean public @NotNull IScopes sentryHub( final @NotNull List> optionsConfigurations, final @NotNull SentryProperties options, + final @NotNull ObjectProvider spanFactory, final @NotNull ObjectProvider gitProperties) { optionsConfigurations.forEach( optionsConfiguration -> optionsConfiguration.configure(options)); @@ -128,6 +150,7 @@ static class HubConfiguration { options.setRelease(git.getCommitId()); } }); + spanFactory.ifAvailable(options::setSpanFactory); options.setSentryClientName( BuildConfig.SENTRY_SPRING_BOOT_SDK_NAME + "/" + BuildConfig.VERSION_NAME); @@ -155,21 +178,6 @@ static class ContextTagsEventProcessorConfiguration { } } - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false") - @ConditionalOnClass(io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor.class) - @SuppressWarnings("deprecation") - @Open - static class OpenTelemetryLinkErrorEventProcessorConfiguration { - - @Bean - @ConditionalOnMissingBean - public @NotNull io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor - openTelemetryLinkErrorEventProcessor() { - return new io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor(); - } - } - @Configuration(proxyBeanMethods = false) @Import(SentryGraphqlAutoConfiguration.class) @Open diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index 2cb711d3501..a9a52c311f6 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -1,6 +1,7 @@ package io.sentry.spring.boot import com.acme.MainBootClass +import io.opentelemetry.api.OpenTelemetry import io.sentry.AsyncHttpTransportFactory import io.sentry.Breadcrumb import io.sentry.EventProcessor @@ -12,10 +13,12 @@ import io.sentry.NoOpTransportFactory import io.sentry.SamplingContext import io.sentry.Sentry import io.sentry.SentryEvent +import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.checkEvent -import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor +import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider +import io.sentry.opentelemetry.agent.AgentMarker import io.sentry.protocol.SentryTransaction import io.sentry.protocol.User import io.sentry.quartz.SentryJobListener @@ -725,43 +728,72 @@ class SentryAutoConfigurationTest { } @Test - fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath and auto init off, creates OpenTelemetryLinkErrorEventProcessor`() { + fun `when AgentMarker is on the classpath and auto init off, runs SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false") .run { - assertThat(it).hasSingleBean(OpenTelemetryLinkErrorEventProcessor::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.eventProcessors).anyMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + assertTrue(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit")) } } @Test - fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init on, does not create OpenTelemetryLinkErrorEventProcessor`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=true") + fun `when AgentMarker is on the classpath and auto init on, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .run { - assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit")) } } @Test - fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init default, does not create OpenTelemetryLinkErrorEventProcessor`() { + fun `when AgentMarker is not on the classpath and auto init off, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false") + .withClassLoader(FilteredClassLoader(AgentMarker::class.java)) + .run { + assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit")) + } + } + + @Test + fun `when AgentMarker is not on the classpath but OpenTelemetry is, runs SpringBoot3OpenTelemetryNoAgent`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withClassLoader(FilteredClassLoader(AgentMarker::class.java)) + .withUserConfiguration(OtelBeanConfig::class.java) .run { - assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + assertTrue(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent")) } } @Test - fun `when OpenTelemetryLinkErrorEventProcessor is not on the classpath, does not create OpenTelemetryLinkErrorEventProcessor`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.auto-init=false") - .withClassLoader(FilteredClassLoader(OpenTelemetryLinkErrorEventProcessor::class.java)) + fun `when AgentMarker and OpenTelemetry are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withClassLoader(FilteredClassLoader(AgentMarker::class.java, OpenTelemetry::class.java)) .run { - assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) - val options = it.getBean(SentryOptions::class.java) - assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent")) + } + } + + @Test + fun `when AgentMarker and SentryAutoConfigurationCustomizerProvider are not on the classpath, does not run SpringBoot3OpenTelemetryNoAgent`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withClassLoader(FilteredClassLoader(AgentMarker::class.java, SentryAutoConfigurationCustomizerProvider::class.java)) + .withUserConfiguration(OtelBeanConfig::class.java) + .run { + assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent")) + } + } + + @Test + fun `when AgentMarker is not on the classpath and auto init on, does not run SentryOpenTelemetryAgentWithoutAutoInitConfiguration`() { + SentryIntegrationPackageStorage.getInstance().clearStorage() + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withClassLoader(FilteredClassLoader(AgentMarker::class.java)) + .run { + assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryAgentWithoutAutoInit")) } } @@ -1011,6 +1043,16 @@ class SentryAutoConfigurationTest { override fun sample(samplingContext: SamplingContext) = 1.0 } + /** + * this should be taken care of by the otel spring starter in a real application + */ + @Configuration + open class OtelBeanConfig { + + @Bean + open fun openTelemetry() = OpenTelemetry.noop() + } + open class CustomSentryUserProvider : SentryUserProvider { override fun provideUser(): User? { val user = User() diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/it/SentrySpringIntegrationTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/it/SentrySpringIntegrationTest.kt index bb6aa99fe9e..8d0013dae90 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/it/SentrySpringIntegrationTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/it/SentrySpringIntegrationTest.kt @@ -1,8 +1,10 @@ package io.sentry.spring.boot.it +import io.sentry.DefaultSpanFactory import io.sentry.IScopes import io.sentry.ITransportFactory import io.sentry.Sentry +import io.sentry.SentryOptions import io.sentry.checkEvent import io.sentry.checkTransaction import io.sentry.spring.tracing.SentrySpan @@ -223,6 +225,12 @@ open class App { @Bean open fun mockTransport() = transport + + @Bean + open fun optionsCallback() = Sentry.OptionsConfiguration { options -> + // due to OTel being on the classpath we need to set the default again + options.spanFactory = DefaultSpanFactory() + } } @RestController diff --git a/sentry-spring/api/sentry-spring.api b/sentry-spring/api/sentry-spring.api index 84b9ddd133b..ebc918f0779 100644 --- a/sentry-spring/api/sentry-spring.api +++ b/sentry-spring/api/sentry-spring.api @@ -194,6 +194,17 @@ public final class io/sentry/spring/graphql/SentrySpringSubscriptionHandler : io public fun onSubscriptionResult (Ljava/lang/Object;Lio/sentry/IScopes;Lio/sentry/graphql/ExceptionReporter;Lgraphql/execution/instrumentation/parameters/InstrumentationFieldFetchParameters;)Ljava/lang/Object; } +public class io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration { + public fun ()V + public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration; +} + +public class io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration { + public fun ()V + public static fun openTelemetrySpanFactory (Lio/opentelemetry/api/OpenTelemetry;)Lio/sentry/ISpanFactory; + public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration; +} + public class io/sentry/spring/tracing/SentryAdviceConfiguration { public fun ()V public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice; diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index ac444c25ca3..8f7ed519d2e 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -38,6 +38,9 @@ dependencies { compileOnly(projects.sentryGraphql) compileOnly(Config.Libs.springBootStarterQuartz) compileOnly(projects.sentryQuartz) + compileOnly(Config.Libs.OpenTelemetry.otelSdk) + compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization) + compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap) compileOnly(Config.CompileOnly.nopen) errorprone(Config.CompileOnly.nopenChecker) diff --git a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java new file mode 100644 index 00000000000..2078891ec5e --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java @@ -0,0 +1,27 @@ +package io.sentry.spring.opentelemetry; + +import com.jakewharton.nopen.annotation.Open; +import io.sentry.Sentry; +import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryOptions; +import io.sentry.opentelemetry.OpenTelemetryUtil; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Open +public class SentryOpenTelemetryAgentWithoutAutoInitConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration") + public @NotNull Sentry.OptionsConfiguration + sentryOpenTelemetryOptionsConfiguration() { + return options -> { + SentryIntegrationPackageStorage.getInstance() + .addIntegration("SpringBoot3OpenTelemetryAgentWithoutAutoInit"); + OpenTelemetryUtil.applyOpenTelemetryOptions(options, true); + }; + } +} diff --git a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java new file mode 100644 index 00000000000..a59f037ba4c --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java @@ -0,0 +1,38 @@ +package io.sentry.spring.opentelemetry; + +import com.jakewharton.nopen.annotation.Open; +import io.opentelemetry.api.OpenTelemetry; +import io.sentry.ISpanFactory; +import io.sentry.Sentry; +import io.sentry.SentryIntegrationPackageStorage; +import io.sentry.SentryOptions; +import io.sentry.opentelemetry.OpenTelemetryUtil; +import io.sentry.opentelemetry.OtelSpanFactory; +import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@Open +public class SentryOpenTelemetryNoAgentConfiguration { + + @Bean + @ConditionalOnMissingBean + public static ISpanFactory openTelemetrySpanFactory(OpenTelemetry openTelemetry) { + return new OtelSpanFactory(openTelemetry); + } + + @Bean + @ConditionalOnMissingBean(name = "sentryOpenTelemetryOptionsConfiguration") + public @NotNull Sentry.OptionsConfiguration + sentryOpenTelemetryOptionsConfiguration() { + return options -> { + SentryIntegrationPackageStorage.getInstance() + .addIntegration("SpringBoot3OpenTelemetryNoAgent"); + SentryAutoConfigurationCustomizerProvider.skipInit = true; + OpenTelemetryUtil.applyOpenTelemetryOptions(options, false); + }; + } +} From 68c3c2aa50dc09803206b265d192acf13803fa75 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 12 Nov 2024 14:33:31 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Lukas Bloder --- .../SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java | 2 +- .../opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java index 2078891ec5e..bfc613512e8 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java +++ b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryAgentWithoutAutoInitConfiguration.java @@ -20,7 +20,7 @@ public class SentryOpenTelemetryAgentWithoutAutoInitConfiguration { sentryOpenTelemetryOptionsConfiguration() { return options -> { SentryIntegrationPackageStorage.getInstance() - .addIntegration("SpringBoot3OpenTelemetryAgentWithoutAutoInit"); + .addIntegration("SpringBootOpenTelemetryAgentWithoutAutoInit"); OpenTelemetryUtil.applyOpenTelemetryOptions(options, true); }; } diff --git a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java index a59f037ba4c..61ee3aa1e73 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java +++ b/sentry-spring/src/main/java/io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfiguration.java @@ -30,7 +30,7 @@ public static ISpanFactory openTelemetrySpanFactory(OpenTelemetry openTelemetry) sentryOpenTelemetryOptionsConfiguration() { return options -> { SentryIntegrationPackageStorage.getInstance() - .addIntegration("SpringBoot3OpenTelemetryNoAgent"); + .addIntegration("SpringBootOpenTelemetryNoAgent"); SentryAutoConfigurationCustomizerProvider.skipInit = true; OpenTelemetryUtil.applyOpenTelemetryOptions(options, false); };