diff --git a/CHANGELOG.md b/CHANGELOG.md index 103651ac21..3362fa74ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes - Add rate limit to Metrics ([#3334](https://github.com/getsentry/sentry-java/pull/3334)) +- Fix java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping in Spring Boot Servlet mode without WebMVC ([#3336](https://github.com/getsentry/sentry-java/pull/3336)) ## 7.7.0 diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java index 8a55208e21..7893cf7c7c 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java @@ -33,6 +33,7 @@ import io.sentry.spring.jakarta.tracing.SentryTracingFilter; import io.sentry.spring.jakarta.tracing.SentryTransactionPointcutConfiguration; import io.sentry.spring.jakarta.tracing.SpringMvcTransactionNameProvider; +import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider; import io.sentry.spring.jakarta.tracing.TransactionNameProvider; import io.sentry.transport.ITransportGate; import io.sentry.transport.apache.ApacheHttpClientTransportFactory; @@ -49,6 +50,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; @@ -267,12 +269,6 @@ static class SentrySecurityConfiguration { return new SentryRequestResolver(hub); } - @Bean - @ConditionalOnMissingBean(TransactionNameProvider.class) - public @NotNull TransactionNameProvider transactionNameProvider() { - return new SpringMvcTransactionNameProvider(); - } - @Bean @ConditionalOnMissingBean(name = "sentrySpringFilter") public @NotNull FilterRegistrationBean sentrySpringFilter( @@ -296,11 +292,10 @@ public FilterRegistrationBean sentryTracingFilter( return filter; } - /** Wraps exception resolver @Bean because the return type is loaded too early otherwise */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HandlerExceptionResolver.class) @Open - static class SentryExceptionResolverConfigurationWrapper { + static class SentryMvcModeConfig { @Bean @ConditionalOnMissingBean @@ -311,6 +306,24 @@ static class SentryExceptionResolverConfigurationWrapper { return new SentryExceptionResolver( sentryHub, transactionNameProvider, options.getExceptionResolverOrder()); } + + @Bean + @ConditionalOnMissingBean(TransactionNameProvider.class) + public @NotNull TransactionNameProvider transactionNameProvider() { + return new SpringMvcTransactionNameProvider(); + } + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver") + @Open + static class SentryServletModeConfig { + + @Bean + @ConditionalOnMissingBean(TransactionNameProvider.class) + public @NotNull TransactionNameProvider transactionNameProvider() { + return new SpringServletTransactionNameProvider(); + } } } diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt index f99a8b7b5a..3d4b16d419 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt @@ -26,6 +26,8 @@ import io.sentry.spring.jakarta.SentryUserFilter import io.sentry.spring.jakarta.SentryUserProvider import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider import io.sentry.spring.jakarta.tracing.SentryTracingFilter +import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider +import io.sentry.spring.jakarta.tracing.TransactionNameProvider import io.sentry.transport.ITransport import io.sentry.transport.ITransportGate import io.sentry.transport.apache.ApacheHttpClientTransportFactory @@ -445,6 +447,15 @@ class SentryAutoConfigurationTest { } } + @Test + fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true") + .withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java)) + .run { + assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(SpringServletTransactionNameProvider::class.java) + } + } + @Test fun `when tracing is enabled, creates tracing filter`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0") 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 ac2fd57e98..1c66a98c50 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 @@ -33,6 +33,7 @@ import io.sentry.spring.tracing.SentryTracingFilter; import io.sentry.spring.tracing.SentryTransactionPointcutConfiguration; import io.sentry.spring.tracing.SpringMvcTransactionNameProvider; +import io.sentry.spring.tracing.SpringServletTransactionNameProvider; import io.sentry.spring.tracing.TransactionNameProvider; import io.sentry.transport.ITransportGate; import io.sentry.transport.apache.ApacheHttpClientTransportFactory; @@ -49,6 +50,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; @@ -265,12 +267,6 @@ static class SentrySecurityConfiguration { return new SentryRequestResolver(hub); } - @Bean - @ConditionalOnMissingBean(TransactionNameProvider.class) - public @NotNull TransactionNameProvider transactionNameProvider() { - return new SpringMvcTransactionNameProvider(); - } - @Bean @ConditionalOnMissingBean(name = "sentrySpringFilter") public @NotNull FilterRegistrationBean sentrySpringFilter( @@ -294,11 +290,10 @@ public FilterRegistrationBean sentryTracingFilter( return filter; } - /** Wraps exception resolver @Bean because the return type is loaded too early otherwise */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HandlerExceptionResolver.class) @Open - static class SentryExceptionResolverConfigurationWrapper { + static class SentryMvcModeConfig { @Bean @ConditionalOnMissingBean @@ -309,6 +304,24 @@ static class SentryExceptionResolverConfigurationWrapper { return new SentryExceptionResolver( sentryHub, transactionNameProvider, options.getExceptionResolverOrder()); } + + @Bean + @ConditionalOnMissingBean(TransactionNameProvider.class) + public @NotNull TransactionNameProvider transactionNameProvider() { + return new SpringMvcTransactionNameProvider(); + } + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver") + @Open + static class SentryServletModeConfig { + + @Bean + @ConditionalOnMissingBean(TransactionNameProvider.class) + public @NotNull TransactionNameProvider transactionNameProvider() { + return new SpringServletTransactionNameProvider(); + } } } 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 f3f3d0cf1a..86785f409b 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 @@ -26,6 +26,8 @@ import io.sentry.spring.SentryUserFilter import io.sentry.spring.SentryUserProvider import io.sentry.spring.SpringSecuritySentryUserProvider import io.sentry.spring.tracing.SentryTracingFilter +import io.sentry.spring.tracing.SpringServletTransactionNameProvider +import io.sentry.spring.tracing.TransactionNameProvider import io.sentry.transport.ITransport import io.sentry.transport.ITransportGate import io.sentry.transport.apache.ApacheHttpClientTransportFactory @@ -444,6 +446,17 @@ class SentryAutoConfigurationTest { } } + @Test + fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true") + .withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java)) + .run { + assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf( + SpringServletTransactionNameProvider::class.java + ) + } + } + @Test fun `when tracing is enabled, creates tracing filter`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0") diff --git a/sentry-spring-jakarta/api/sentry-spring-jakarta.api b/sentry-spring-jakarta/api/sentry-spring-jakarta.api index cebbd26b4f..543f14823b 100644 --- a/sentry-spring-jakarta/api/sentry-spring-jakarta.api +++ b/sentry-spring-jakarta/api/sentry-spring-jakarta.api @@ -261,6 +261,12 @@ public final class io/sentry/spring/jakarta/tracing/SpringMvcTransactionNameProv public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; } +public final class io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider : io/sentry/spring/jakarta/tracing/TransactionNameProvider { + public fun ()V + public fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String; + public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; +} + public abstract interface class io/sentry/spring/jakarta/tracing/TransactionNameProvider { public abstract fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String; public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider.java new file mode 100644 index 0000000000..e0beda65a0 --- /dev/null +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider.java @@ -0,0 +1,22 @@ +package io.sentry.spring.jakarta.tracing; + +import io.sentry.protocol.TransactionNameSource; +import jakarta.servlet.http.HttpServletRequest; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */ +@ApiStatus.Internal +public final class SpringServletTransactionNameProvider implements TransactionNameProvider { + @Override + public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) { + return request.getMethod() + " " + request.getRequestURI(); + } + + @Override + @ApiStatus.Internal + public @NotNull TransactionNameSource provideTransactionSource() { + return TransactionNameSource.URL; + } +} diff --git a/sentry-spring/api/sentry-spring.api b/sentry-spring/api/sentry-spring.api index 9ef6b5bb3a..f0d792724d 100644 --- a/sentry-spring/api/sentry-spring.api +++ b/sentry-spring/api/sentry-spring.api @@ -260,6 +260,12 @@ public final class io/sentry/spring/tracing/SpringMvcTransactionNameProvider : i public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; } +public final class io/sentry/spring/tracing/SpringServletTransactionNameProvider : io/sentry/spring/tracing/TransactionNameProvider { + public fun ()V + public fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String; + public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; +} + public abstract interface class io/sentry/spring/tracing/TransactionNameProvider { public abstract fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String; public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource; diff --git a/sentry-spring/src/main/java/io/sentry/spring/tracing/SpringServletTransactionNameProvider.java b/sentry-spring/src/main/java/io/sentry/spring/tracing/SpringServletTransactionNameProvider.java new file mode 100644 index 0000000000..0e8118e632 --- /dev/null +++ b/sentry-spring/src/main/java/io/sentry/spring/tracing/SpringServletTransactionNameProvider.java @@ -0,0 +1,22 @@ +package io.sentry.spring.tracing; + +import io.sentry.protocol.TransactionNameSource; +import javax.servlet.http.HttpServletRequest; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */ +@ApiStatus.Internal +public final class SpringServletTransactionNameProvider implements TransactionNameProvider { + @Override + public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) { + return request.getMethod() + " " + request.getRequestURI(); + } + + @Override + @ApiStatus.Internal + public @NotNull TransactionNameSource provideTransactionSource() { + return TransactionNameSource.URL; + } +}