diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cd5c9c6a8..6da510909f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Features - Add logging for OpenTelemetry integration ([#2425](https://github.com/getsentry/sentry-java/pull/2425)) +- Auto add `OpenTelemetryLinkErrorEventProcessor` for Spring Boot ([#2429](https://github.com/getsentry/sentry-java/pull/2429)) ## 6.10.0 diff --git a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/build.gradle.kts index a9b7f3666fb..44528ac46fa 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/build.gradle.kts @@ -20,7 +20,10 @@ tasks.withType().configureEach { dependencies { compileOnly(projects.sentry) - implementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) + implementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) { + exclude(group = "io.opentelemetry") + exclude(group = "io.opentelemetry.javaagent") + } compileOnly(Config.Libs.OpenTelemetry.otelSdk) compileOnly(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-core/build.gradle.kts index c8db1650b28..621a8901c3f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-core/build.gradle.kts @@ -21,7 +21,7 @@ tasks.withType().configureEach { dependencies { compileOnly(projects.sentry) - compileOnly(Config.Libs.OpenTelemetry.otelSdk) + implementation(Config.Libs.OpenTelemetry.otelSdk) compileOnly(Config.Libs.OpenTelemetry.otelSemconv) compileOnly(Config.CompileOnly.nopen) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanProcessor.java index 3a312691092..7ecb732e883 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanProcessor.java @@ -151,7 +151,7 @@ public void onEnd(final @NotNull ReadableSpan otelSpan) { .getLogger() .log( SentryLevel.DEBUG, - "Unable to find Sentry span for OpenTelemetry span %s (trace %s).", + "Unable to find Sentry span for OpenTelemetry span %s (trace %s). This may simply mean it is a Sentry request.", traceData.getSpanId(), traceData.getTraceId()); return; diff --git a/sentry-spring-boot-starter-jakarta/build.gradle.kts b/sentry-spring-boot-starter-jakarta/build.gradle.kts index 34d3e51bf32..5279f3080f3 100644 --- a/sentry-spring-boot-starter-jakarta/build.gradle.kts +++ b/sentry-spring-boot-starter-jakarta/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { compileOnly(Config.Libs.springBoot3StarterAop) compileOnly(Config.Libs.springBoot3StarterSecurity) compileOnly(Config.Libs.reactorCore) + compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryCore) annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure) annotationProcessor(Config.AnnotationProcessors.springBootConfiguration) @@ -77,6 +78,7 @@ dependencies { testImplementation(Config.Libs.springBoot3StarterWebflux) testImplementation(Config.Libs.springBoot3StarterSecurity) testImplementation(Config.Libs.springBoot3StarterAop) + testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) } configure { diff --git a/sentry-spring-boot-starter-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-starter-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index a8daf9d5311..741e8bb1358 100644 --- a/sentry-spring-boot-starter-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -8,6 +8,7 @@ import io.sentry.Integration; import io.sentry.Sentry; import io.sentry.SentryOptions; +import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor; import io.sentry.protocol.SdkVersion; import io.sentry.spring.jakarta.ContextTagsEventProcessor; import io.sentry.spring.jakarta.SentryExceptionResolver; @@ -138,6 +139,19 @@ static class ContextTagsEventProcessorConfiguration { } } + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false") + @ConditionalOnClass(OpenTelemetryLinkErrorEventProcessor.class) + @Open + static class OpenTelemetryLinkErrorEventProcessorConfiguration { + + @Bean + @ConditionalOnMissingBean + public @NotNull OpenTelemetryLinkErrorEventProcessor openTelemetryLinkErrorEventProcessor() { + return new OpenTelemetryLinkErrorEventProcessor(); + } + } + /** Registers beans specific to Spring MVC. */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) diff --git a/sentry-spring-boot-starter-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-starter-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index ba6abeaa7ec..c972e316cb9 100644 --- a/sentry-spring-boot-starter-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-starter-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -15,6 +15,7 @@ import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.checkEvent +import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor import io.sentry.protocol.SentryTransaction import io.sentry.protocol.User import io.sentry.spring.jakarta.ContextTagsEventProcessor @@ -683,6 +684,47 @@ class SentryAutoConfigurationTest { } } + @Test + fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath and auto init off, creates OpenTelemetryLinkErrorEventProcessor`() { + 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 } + } + } + + @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") + .run { + assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + } + } + + @Test + fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init default, does not create OpenTelemetryLinkErrorEventProcessor`() { + 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 } + } + } + + @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)) + .run { + assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + } + } + @Configuration(proxyBeanMethods = false) open class CustomOptionsConfigurationConfiguration { diff --git a/sentry-spring-boot-starter/build.gradle.kts b/sentry-spring-boot-starter/build.gradle.kts index bca7c8daa6a..19f2a43a2d0 100644 --- a/sentry-spring-boot-starter/build.gradle.kts +++ b/sentry-spring-boot-starter/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { compileOnly(Config.Libs.springBootStarterAop) compileOnly(Config.Libs.springBootStarterSecurity) compileOnly(Config.Libs.reactorCore) + compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryCore) annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure) annotationProcessor(Config.AnnotationProcessors.springBootConfiguration) @@ -67,6 +68,7 @@ dependencies { testImplementation(Config.Libs.springBootStarterWebflux) testImplementation(Config.Libs.springBootStarterSecurity) testImplementation(Config.Libs.springBootStarterAop) + testImplementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) } configure { diff --git a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index 908d73c0635..cb33c48c375 100644 --- a/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -8,6 +8,7 @@ import io.sentry.Integration; import io.sentry.Sentry; import io.sentry.SentryOptions; +import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor; import io.sentry.protocol.SdkVersion; import io.sentry.spring.ContextTagsEventProcessor; import io.sentry.spring.SentryExceptionResolver; @@ -139,6 +140,19 @@ static class ContextTagsEventProcessorConfiguration { } } + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "sentry.auto-init", havingValue = "false") + @ConditionalOnClass(OpenTelemetryLinkErrorEventProcessor.class) + @Open + static class OpenTelemetryLinkErrorEventProcessorConfiguration { + + @Bean + @ConditionalOnMissingBean + public @NotNull OpenTelemetryLinkErrorEventProcessor openTelemetryLinkErrorEventProcessor() { + return new OpenTelemetryLinkErrorEventProcessor(); + } + } + /** Registers beans specific to Spring MVC. */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) diff --git a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt index b8ede6e9959..cff7f96d0d3 100644 --- a/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-starter/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt @@ -15,6 +15,7 @@ import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.checkEvent +import io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor import io.sentry.protocol.SentryTransaction import io.sentry.protocol.User import io.sentry.spring.ContextTagsEventProcessor @@ -683,6 +684,47 @@ class SentryAutoConfigurationTest { } } + @Test + fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath and auto init off, creates OpenTelemetryLinkErrorEventProcessor`() { + 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 } + } + } + + @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") + .run { + assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + } + } + + @Test + fun `when OpenTelemetryLinkErrorEventProcessor is on the classpath but auto init default, does not create OpenTelemetryLinkErrorEventProcessor`() { + 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 } + } + } + + @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)) + .run { + assertThat(it).doesNotHaveBean(OpenTelemetryLinkErrorEventProcessor::class.java) + val options = it.getBean(SentryOptions::class.java) + assertThat(options.eventProcessors).noneMatch { processor -> processor.javaClass == OpenTelemetryLinkErrorEventProcessor::class.java } + } + } + @Configuration(proxyBeanMethods = false) open class CustomOptionsConfigurationConfiguration {