From a023a1c8f9e83a40bf3d90842c1e6d5312def2a3 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 10:56:35 +0100 Subject: [PATCH 01/13] Report active Spring profiles in trace context --- .../boot/jakarta/SentryAutoConfiguration.java | 11 +++ .../jakarta/SpringProfilesEventProcessor.java | 57 ++++++++++++ .../jakarta/SentryAutoConfigurationTest.kt | 93 +++++++++++++++---- .../SpringProfilesEventProcessorTest.kt | 92 ++++++++++++++++++ .../spring/boot/SentryAutoConfiguration.java | 11 +++ .../boot/SpringProfilesEventProcessor.java | 57 ++++++++++++ .../boot/SentryAutoConfigurationTest.kt | 8 ++ .../boot/SpringProfilesEventProcessorTest.kt | 82 ++++++++++++++++ 8 files changed, 394 insertions(+), 17 deletions(-) create mode 100644 sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java create mode 100644 sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt create mode 100644 sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java create mode 100644 sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt 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 54800b58310..273fe84cc96 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 @@ -68,6 +68,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.security.core.context.SecurityContextHolder; @@ -470,4 +471,14 @@ private static class SentryTracesSampleRateCondition {} @SuppressWarnings("UnusedNestedClass") private static class SentryTracesSamplerBeanCondition {} } + + @Configuration(proxyBeanMethods = false) + @Open + static class SpringProfilesEventProcessorConfiguration { + @Bean + public @NotNull SpringProfilesEventProcessor springProfilesEventProcessor( + final Environment environment) { + return new SpringProfilesEventProcessor(environment); + } + } } diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java new file mode 100644 index 00000000000..aeba1cec1d5 --- /dev/null +++ b/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java @@ -0,0 +1,57 @@ +package io.sentry.spring.boot.jakarta; + +import io.sentry.EventProcessor; +import io.sentry.Hint; +import io.sentry.SentryBaseEvent; +import io.sentry.SentryEvent; +import io.sentry.SentryReplayEvent; +import io.sentry.SpanContext; +import io.sentry.protocol.SentryTransaction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.core.env.Environment; + +/** + * Attaches the list of active Spring profiles (an empty list if only the default profile is active) + * to the {@link io.sentry.TraceContext} associated with the event. + */ +public final class SpringProfilesEventProcessor implements EventProcessor { + private static final @NotNull String ACTIVE_PROFILES_TRACE_CONTEXT_KEY = "spring.active_profiles"; + + private final @NotNull Environment environment; + + @Override + public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { + processInternal(event); + return event; + } + + @Override + public @NotNull SentryTransaction process( + final @NotNull SentryTransaction transaction, final @NotNull Hint hint) { + processInternal(transaction); + return transaction; + } + + @Override + public @NotNull SentryReplayEvent process( + final @NotNull SentryReplayEvent event, final @NotNull Hint hint) { + processInternal(event); + return event; + } + + public void processInternal(final @NotNull SentryBaseEvent event) { + @Nullable SpanContext trace = event.getContexts().getTrace(); + if (trace != null) { + @Nullable String[] activeProfiles = environment.getActiveProfiles(); + if (activeProfiles == null) { + activeProfiles = new String[0]; + } + trace.setData(ACTIVE_PROFILES_TRACE_CONTEXT_KEY, activeProfiles); + } + } + + public SpringProfilesEventProcessor(final @NotNull Environment environment) { + this.environment = environment; + } +} 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 3498c903194..650855cbdc1 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 @@ -80,7 +80,12 @@ import kotlin.test.assertTrue class SentryAutoConfigurationTest { private val contextRunner = WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) + .withConfiguration( + AutoConfigurations.of( + SentryAutoConfiguration::class.java, + WebMvcAutoConfiguration::class.java + ) + ) @Test fun `scopes is not created when auto-configuration dsn is not set`() { @@ -211,13 +216,19 @@ class SentryAutoConfigurationTest { assertThat(options.proxy!!.pass).isEqualTo("proxy-pass") assertThat(options.tracesSampleRate).isEqualTo(0.3) assertThat(options.tags).containsEntry("tag1", "tag1-value").containsEntry("tag2", "tag2-value") - assertThat(options.ignoredExceptionsForType).containsOnly(RuntimeException::class.java, IllegalStateException::class.java) + assertThat(options.ignoredExceptionsForType).containsOnly( + RuntimeException::class.java, + IllegalStateException::class.java + ) assertThat(options.tracePropagationTargets).containsOnly("localhost", "^(http|https)://api\\..*\$") assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) assertThat(options.ignoredErrors).containsOnly(FilterString("Some error"), FilterString("Another .*")) - assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) + assertThat(options.ignoredTransactions).containsOnly( + FilterString("transactionName1"), + FilterString("transactionNameB") + ) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) assertThat(options.isGlobalHubMode).isEqualTo(true) @@ -319,7 +330,9 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withUserConfiguration(CustomBeforeSendTransactionCallbackConfiguration::class.java) .run { - assertThat(it.getBean(SentryOptions::class.java).beforeSendTransaction).isInstanceOf(CustomBeforeSendTransactionCallback::class.java) + assertThat(it.getBean(SentryOptions::class.java).beforeSendTransaction).isInstanceOf( + CustomBeforeSendTransactionCallback::class.java + ) } } @@ -328,7 +341,9 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withUserConfiguration(CustomBeforeBreadcrumbCallbackConfiguration::class.java) .run { - assertThat(it.getBean(SentryOptions::class.java).beforeBreadcrumb).isInstanceOf(CustomBeforeBreadcrumbCallback::class.java) + assertThat(it.getBean(SentryOptions::class.java).beforeBreadcrumb).isInstanceOf( + CustomBeforeBreadcrumbCallback::class.java + ) } } @@ -455,7 +470,9 @@ class SentryAutoConfigurationTest { 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) + assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf( + SpringServletTransactionNameProvider::class.java + ) } } @@ -703,7 +720,9 @@ class SentryAutoConfigurationTest { fun `when sentry-apache-http-client-5 is on the classpath, creates apache transport factory`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .run { - assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf(ApacheHttpClientTransportFactory::class.java) + assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf( + ApacheHttpClientTransportFactory::class.java + ) } } @@ -712,7 +731,9 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withClassLoader(FilteredClassLoader(ApacheHttpClientTransportFactory::class.java)) .run { - assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf(AsyncHttpTransportFactory::class.java) + assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf( + AsyncHttpTransportFactory::class.java + ) } } @@ -801,7 +822,12 @@ class SentryAutoConfigurationTest { 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)) + .withClassLoader( + FilteredClassLoader( + AgentMarker::class.java, + SentryAutoConfigurationCustomizerProvider::class.java + ) + ) .withUserConfiguration(OtelBeanConfig::class.java) .run { assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent")) @@ -820,7 +846,10 @@ class SentryAutoConfigurationTest { @Test fun `creates quartz config`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) .run { assertThat(it).hasSingleBean(SchedulerFactoryBeanCustomizer::class.java) } @@ -828,7 +857,10 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if quartz lib missing`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) .withClassLoader(FilteredClassLoader(QuartzScheduler::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -837,7 +869,10 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if spring-quartz lib missing`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) .withClassLoader(FilteredClassLoader(SchedulerFactoryBean::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -846,7 +881,10 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if sentry-quartz lib missing`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) .withClassLoader(FilteredClassLoader(SentryJobListener::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -899,7 +937,10 @@ class SentryAutoConfigurationTest { @Test fun `Sentry quartz job listener is added`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) .withUserConfiguration(QuartzAutoConfiguration::class.java) .run { val jobListeners = it.getBean(Scheduler::class.java).listenerManager.jobListeners @@ -913,8 +954,14 @@ class SentryAutoConfigurationTest { @Test fun `user defined SchedulerFactoryBeanCustomizer overrides Sentry Customizer`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") - .withUserConfiguration(QuartzAutoConfiguration::class.java, CustomSchedulerFactoryBeanCustomizerConfiguration::class.java) + contextRunner.withPropertyValues( + "sentry.dsn=http://key@localhost/proj", + "sentry.enable-automatic-checkins=true" + ) + .withUserConfiguration( + QuartzAutoConfiguration::class.java, + CustomSchedulerFactoryBeanCustomizerConfiguration::class.java + ) .run { val jobListeners = it.getBean(Scheduler::class.java).listenerManager.jobListeners assertThat(jobListeners).hasSize(1) @@ -948,7 +995,11 @@ class SentryAutoConfigurationTest { @Bean open fun mySchedulerFactoryBeanCustomizer(): SchedulerFactoryBeanCustomizer { - return SchedulerFactoryBeanCustomizer { schedulerFactoryBean -> schedulerFactoryBean.setGlobalJobListeners(MyJobListener()) } + return SchedulerFactoryBeanCustomizer { schedulerFactoryBean -> + schedulerFactoryBean.setGlobalJobListeners( + MyJobListener() + ) + } } } @@ -1172,4 +1223,12 @@ class SentryAutoConfigurationTest { val userFilter = this.getBean("sentryUserFilter", FilterRegistrationBean::class.java).filter as SentryUserFilter return userFilter.sentryUserProviders } + + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } + } } diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt new file mode 100644 index 00000000000..f02d6cc107d --- /dev/null +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt @@ -0,0 +1,92 @@ +package io.sentry.spring.boot.jakarta + +import io.sentry.ITransportFactory +import io.sentry.Sentry +import io.sentry.SentryOptions +import io.sentry.checkEvent +import io.sentry.transport.ITransport +import org.assertj.core.api.Assertions.assertThat +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.springframework.boot.autoconfigure.AutoConfigurations +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration +import org.springframework.boot.test.context.runner.WebApplicationContextRunner +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import kotlin.test.Test + +class SpringProfilesEventProcessorTest { + + private val contextRunner = WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) + .withUserConfiguration(MockTransportConfiguration::class.java) + + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean + open fun sentryTransport() = transport + } + + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } + } + + @Test + fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withUserConfiguration(MockTransportConfiguration::class.java) + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) + }, + anyOrNull() + ) + } + } + + @Test + fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { + contextRunner + .withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withUserConfiguration(MockTransportConfiguration::class.java) + .withPropertyValues( + "spring.profiles.active=test1,test2" + ) + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) + }, + anyOrNull() + ) + } + } +} 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 7476b756dd6..7710830acd4 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 @@ -67,6 +67,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.security.core.context.SecurityContextHolder; @@ -439,4 +440,14 @@ private static class SentryTracesSampleRateCondition {} @SuppressWarnings("UnusedNestedClass") private static class SentryTracesSamplerBeanCondition {} } + + @Configuration(proxyBeanMethods = false) + @Open + static class SpringProfilesEventProcessorConfiguration { + @Bean + public @NotNull SpringProfilesEventProcessor springProfilesEventProcessor( + final Environment environment) { + return new SpringProfilesEventProcessor(environment); + } + } } diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java new file mode 100644 index 00000000000..5669611ab85 --- /dev/null +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java @@ -0,0 +1,57 @@ +package io.sentry.spring.boot; + +import io.sentry.EventProcessor; +import io.sentry.Hint; +import io.sentry.SentryBaseEvent; +import io.sentry.SentryEvent; +import io.sentry.SentryReplayEvent; +import io.sentry.SpanContext; +import io.sentry.protocol.SentryTransaction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.core.env.Environment; + +/** + * Attaches the list of active Spring profiles (an empty list if only the default profile is active) + * to the {@link io.sentry.TraceContext} associated with the event. + */ +public final class SpringProfilesEventProcessor implements EventProcessor { + private static final @NotNull String ACTIVE_PROFILES_TRACE_CONTEXT_KEY = "spring.active_profiles"; + + private final @NotNull Environment environment; + + @Override + public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) { + processInternal(event); + return event; + } + + @Override + public @NotNull SentryTransaction process( + final @NotNull SentryTransaction transaction, final @NotNull Hint hint) { + processInternal(transaction); + return transaction; + } + + @Override + public @NotNull SentryReplayEvent process( + final @NotNull SentryReplayEvent event, final @NotNull Hint hint) { + processInternal(event); + return event; + } + + public void processInternal(final @NotNull SentryBaseEvent event) { + @Nullable SpanContext trace = event.getContexts().getTrace(); + if (trace != null) { + @Nullable String[] activeProfiles = environment.getActiveProfiles(); + if (activeProfiles == null) { + activeProfiles = new String[0]; + } + trace.setData(ACTIVE_PROFILES_TRACE_CONTEXT_KEY, activeProfiles); + } + } + + public SpringProfilesEventProcessor(final @NotNull Environment environment) { + this.environment = environment; + } +} 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 cd105dab445..5e36344d670 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 @@ -1112,4 +1112,12 @@ class SentryAutoConfigurationTest { val userFilter = this.getBean("sentryUserFilter", FilterRegistrationBean::class.java).filter as SentryUserFilter return userFilter.sentryUserProviders } + + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } + } } diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt new file mode 100644 index 00000000000..a561e7baa30 --- /dev/null +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt @@ -0,0 +1,82 @@ +package io.sentry.spring.boot + +import io.sentry.ITransportFactory +import io.sentry.Sentry +import io.sentry.checkEvent +import io.sentry.spring.boot.SentryAutoConfigurationTest.MockTransportConfiguration +import io.sentry.transport.ITransport +import org.assertj.core.api.Assertions.assertThat +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.springframework.boot.autoconfigure.AutoConfigurations +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration +import org.springframework.boot.test.context.runner.WebApplicationContextRunner +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import kotlin.test.Test + +class SpringProfilesEventProcessorTest { + + private val contextRunner = WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) + .withUserConfiguration(MockTransportConfiguration::class.java) + + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean + open fun sentryTransport() = transport + } + + @Test + fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) + }, + anyOrNull() + ) + } + } + + @Test + fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { + contextRunner + .withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withPropertyValues( + "spring.profiles.active=test1,test2" + ) + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) + }, + anyOrNull() + ) + } + } +} From d4edf04a79f719c40661a4f74985aaa0dbc15dc8 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 11:02:27 +0100 Subject: [PATCH 02/13] formatting etc --- .../jakarta/SentryAutoConfigurationTest.kt | 93 ++++--------------- .../boot/SentryAutoConfigurationTest.kt | 8 -- 2 files changed, 17 insertions(+), 84 deletions(-) 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 650855cbdc1..3498c903194 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 @@ -80,12 +80,7 @@ import kotlin.test.assertTrue class SentryAutoConfigurationTest { private val contextRunner = WebApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of( - SentryAutoConfiguration::class.java, - WebMvcAutoConfiguration::class.java - ) - ) + .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) @Test fun `scopes is not created when auto-configuration dsn is not set`() { @@ -216,19 +211,13 @@ class SentryAutoConfigurationTest { assertThat(options.proxy!!.pass).isEqualTo("proxy-pass") assertThat(options.tracesSampleRate).isEqualTo(0.3) assertThat(options.tags).containsEntry("tag1", "tag1-value").containsEntry("tag2", "tag2-value") - assertThat(options.ignoredExceptionsForType).containsOnly( - RuntimeException::class.java, - IllegalStateException::class.java - ) + assertThat(options.ignoredExceptionsForType).containsOnly(RuntimeException::class.java, IllegalStateException::class.java) assertThat(options.tracePropagationTargets).containsOnly("localhost", "^(http|https)://api\\..*\$") assertThat(options.isEnabled).isEqualTo(false) assertThat(options.isSendModules).isEqualTo(false) assertThat(options.ignoredCheckIns).containsOnly(FilterString("slug1"), FilterString("slugB")) assertThat(options.ignoredErrors).containsOnly(FilterString("Some error"), FilterString("Another .*")) - assertThat(options.ignoredTransactions).containsOnly( - FilterString("transactionName1"), - FilterString("transactionNameB") - ) + assertThat(options.ignoredTransactions).containsOnly(FilterString("transactionName1"), FilterString("transactionNameB")) assertThat(options.isEnableBackpressureHandling).isEqualTo(false) assertThat(options.isForceInit).isEqualTo(true) assertThat(options.isGlobalHubMode).isEqualTo(true) @@ -330,9 +319,7 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withUserConfiguration(CustomBeforeSendTransactionCallbackConfiguration::class.java) .run { - assertThat(it.getBean(SentryOptions::class.java).beforeSendTransaction).isInstanceOf( - CustomBeforeSendTransactionCallback::class.java - ) + assertThat(it.getBean(SentryOptions::class.java).beforeSendTransaction).isInstanceOf(CustomBeforeSendTransactionCallback::class.java) } } @@ -341,9 +328,7 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withUserConfiguration(CustomBeforeBreadcrumbCallbackConfiguration::class.java) .run { - assertThat(it.getBean(SentryOptions::class.java).beforeBreadcrumb).isInstanceOf( - CustomBeforeBreadcrumbCallback::class.java - ) + assertThat(it.getBean(SentryOptions::class.java).beforeBreadcrumb).isInstanceOf(CustomBeforeBreadcrumbCallback::class.java) } } @@ -470,9 +455,7 @@ class SentryAutoConfigurationTest { 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 - ) + assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(SpringServletTransactionNameProvider::class.java) } } @@ -720,9 +703,7 @@ class SentryAutoConfigurationTest { fun `when sentry-apache-http-client-5 is on the classpath, creates apache transport factory`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .run { - assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf( - ApacheHttpClientTransportFactory::class.java - ) + assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf(ApacheHttpClientTransportFactory::class.java) } } @@ -731,9 +712,7 @@ class SentryAutoConfigurationTest { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") .withClassLoader(FilteredClassLoader(ApacheHttpClientTransportFactory::class.java)) .run { - assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf( - AsyncHttpTransportFactory::class.java - ) + assertThat(it.getBean(SentryOptions::class.java).transportFactory).isInstanceOf(AsyncHttpTransportFactory::class.java) } } @@ -822,12 +801,7 @@ class SentryAutoConfigurationTest { 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 - ) - ) + .withClassLoader(FilteredClassLoader(AgentMarker::class.java, SentryAutoConfigurationCustomizerProvider::class.java)) .withUserConfiguration(OtelBeanConfig::class.java) .run { assertFalse(SentryIntegrationPackageStorage.getInstance().integrations.contains("SpringBoot3OpenTelemetryNoAgent")) @@ -846,10 +820,7 @@ class SentryAutoConfigurationTest { @Test fun `creates quartz config`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") .run { assertThat(it).hasSingleBean(SchedulerFactoryBeanCustomizer::class.java) } @@ -857,10 +828,7 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if quartz lib missing`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") .withClassLoader(FilteredClassLoader(QuartzScheduler::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -869,10 +837,7 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if spring-quartz lib missing`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") .withClassLoader(FilteredClassLoader(SchedulerFactoryBean::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -881,10 +846,7 @@ class SentryAutoConfigurationTest { @Test fun `does not create quartz config if sentry-quartz lib missing`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") .withClassLoader(FilteredClassLoader(SentryJobListener::class.java)) .run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) @@ -937,10 +899,7 @@ class SentryAutoConfigurationTest { @Test fun `Sentry quartz job listener is added`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") .withUserConfiguration(QuartzAutoConfiguration::class.java) .run { val jobListeners = it.getBean(Scheduler::class.java).listenerManager.jobListeners @@ -954,14 +913,8 @@ class SentryAutoConfigurationTest { @Test fun `user defined SchedulerFactoryBeanCustomizer overrides Sentry Customizer`() { - contextRunner.withPropertyValues( - "sentry.dsn=http://key@localhost/proj", - "sentry.enable-automatic-checkins=true" - ) - .withUserConfiguration( - QuartzAutoConfiguration::class.java, - CustomSchedulerFactoryBeanCustomizerConfiguration::class.java - ) + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.enable-automatic-checkins=true") + .withUserConfiguration(QuartzAutoConfiguration::class.java, CustomSchedulerFactoryBeanCustomizerConfiguration::class.java) .run { val jobListeners = it.getBean(Scheduler::class.java).listenerManager.jobListeners assertThat(jobListeners).hasSize(1) @@ -995,11 +948,7 @@ class SentryAutoConfigurationTest { @Bean open fun mySchedulerFactoryBeanCustomizer(): SchedulerFactoryBeanCustomizer { - return SchedulerFactoryBeanCustomizer { schedulerFactoryBean -> - schedulerFactoryBean.setGlobalJobListeners( - MyJobListener() - ) - } + return SchedulerFactoryBeanCustomizer { schedulerFactoryBean -> schedulerFactoryBean.setGlobalJobListeners(MyJobListener()) } } } @@ -1223,12 +1172,4 @@ class SentryAutoConfigurationTest { val userFilter = this.getBean("sentryUserFilter", FilterRegistrationBean::class.java).filter as SentryUserFilter return userFilter.sentryUserProviders } - - @Test - fun `registers SpringProfilesEventProcessor on SentryOptions`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } - } - } } 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 5e36344d670..cd105dab445 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 @@ -1112,12 +1112,4 @@ class SentryAutoConfigurationTest { val userFilter = this.getBean("sentryUserFilter", FilterRegistrationBean::class.java).filter as SentryUserFilter return userFilter.sentryUserProviders } - - @Test - fun `registers SpringProfilesEventProcessor on SentryOptions`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } - } - } } From 893b7d09352cd6cafa4eaf8a0f216673dd8e89e3 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 11:02:59 +0100 Subject: [PATCH 03/13] formatting --- .../jakarta/SentryAutoConfigurationTest.kt | 8 ++++ .../SpringProfilesEventProcessorTest.kt | 32 +++++++-------- .../boot/SentryAutoConfigurationTest.kt | 8 ++++ .../boot/SpringProfilesEventProcessorTest.kt | 39 ++++++++++++------- 4 files changed, 57 insertions(+), 30 deletions(-) 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 3498c903194..79d3d99563a 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 @@ -925,6 +925,14 @@ class SentryAutoConfigurationTest { } } + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } + } + @Configuration(proxyBeanMethods = false) open class CustomSchedulerFactoryBeanCustomizerConfiguration { class MyJobListener : JobListener { diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt index f02d6cc107d..425654691b6 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt @@ -24,22 +24,6 @@ class SpringProfilesEventProcessorTest { .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) .withUserConfiguration(MockTransportConfiguration::class.java) - @Configuration(proxyBeanMethods = false) - open class MockTransportConfiguration { - - private val transport = mock() - - @Bean - open fun mockTransportFactory(): ITransportFactory { - val factory = mock() - whenever(factory.create(any(), any())).thenReturn(transport) - return factory - } - - @Bean - open fun sentryTransport() = transport - } - @Test fun `registers SpringProfilesEventProcessor on SentryOptions`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") @@ -89,4 +73,20 @@ class SpringProfilesEventProcessorTest { ) } } + + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean + open fun sentryTransport() = transport + } } 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 cd105dab445..e7cdab9f853 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 @@ -865,6 +865,14 @@ class SentryAutoConfigurationTest { } } + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } + } + @Configuration(proxyBeanMethods = false) open class CustomSchedulerFactoryBeanCustomizerConfiguration { class MyJobListener : JobListener { diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt index a561e7baa30..995c27ac91f 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt @@ -2,6 +2,7 @@ package io.sentry.spring.boot import io.sentry.ITransportFactory import io.sentry.Sentry +import io.sentry.SentryOptions import io.sentry.checkEvent import io.sentry.spring.boot.SentryAutoConfigurationTest.MockTransportConfiguration import io.sentry.transport.ITransport @@ -24,25 +25,18 @@ class SpringProfilesEventProcessorTest { .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) .withUserConfiguration(MockTransportConfiguration::class.java) - @Configuration(proxyBeanMethods = false) - open class MockTransportConfiguration { - - private val transport = mock() - - @Bean - open fun mockTransportFactory(): ITransportFactory { - val factory = mock() - whenever(factory.create(any(), any())).thenReturn(transport) - return factory - } - - @Bean - open fun sentryTransport() = transport + @Test + fun `registers SpringProfilesEventProcessor on SentryOptions`() { + contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .run { + assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } + } } @Test fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withUserConfiguration(MockTransportConfiguration::class.java) .run { Sentry.captureMessage("test") val transport = it.getBean(ITransport::class.java) @@ -62,6 +56,7 @@ class SpringProfilesEventProcessorTest { fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { contextRunner .withPropertyValues("sentry.dsn=http://key@localhost/proj") + .withUserConfiguration(MockTransportConfiguration::class.java) .withPropertyValues( "spring.profiles.active=test1,test2" ) @@ -79,4 +74,20 @@ class SpringProfilesEventProcessorTest { ) } } + + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean + open fun sentryTransport() = transport + } } From 441ed8dcaf6b40a919bb4d8f2bd2ce31926cb702 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 11:05:10 +0100 Subject: [PATCH 04/13] remove duplicate test --- .../boot/jakarta/SpringProfilesEventProcessorTest.kt | 9 --------- .../spring/boot/SpringProfilesEventProcessorTest.kt | 9 --------- 2 files changed, 18 deletions(-) diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt index 425654691b6..9fc1d0af589 100644 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt @@ -2,7 +2,6 @@ package io.sentry.spring.boot.jakarta import io.sentry.ITransportFactory import io.sentry.Sentry -import io.sentry.SentryOptions import io.sentry.checkEvent import io.sentry.transport.ITransport import org.assertj.core.api.Assertions.assertThat @@ -24,14 +23,6 @@ class SpringProfilesEventProcessorTest { .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) .withUserConfiguration(MockTransportConfiguration::class.java) - @Test - fun `registers SpringProfilesEventProcessor on SentryOptions`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } - } - } - @Test fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt index 995c27ac91f..764889866ed 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt @@ -2,7 +2,6 @@ package io.sentry.spring.boot import io.sentry.ITransportFactory import io.sentry.Sentry -import io.sentry.SentryOptions import io.sentry.checkEvent import io.sentry.spring.boot.SentryAutoConfigurationTest.MockTransportConfiguration import io.sentry.transport.ITransport @@ -25,14 +24,6 @@ class SpringProfilesEventProcessorTest { .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) .withUserConfiguration(MockTransportConfiguration::class.java) - @Test - fun `registers SpringProfilesEventProcessor on SentryOptions`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .run { - assertThat(it.getBean(SentryOptions::class.java).eventProcessors).anyMatch { processor -> processor.javaClass == SpringProfilesEventProcessor::class.java } - } - } - @Test fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") From 92fe6f5bcee7c9342248274c9fe86176e3a37958 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 11:18:37 +0100 Subject: [PATCH 05/13] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd31db09c2..91682aeeb6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ### Features - Create onCreate and onStart spans for all Activities ([#4025](https://github.com/getsentry/sentry-java/pull/4025)) +- A list of active Spring profiles is attached to the trace context and displayed in the Sentry UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) + - This consists of an empty list when only the default profile is active ### Fixes From b222b9a3dc9bb5d4d0affa4964d75589621eabd8 Mon Sep 17 00:00:00 2001 From: lcian Date: Fri, 7 Feb 2025 11:38:40 +0100 Subject: [PATCH 06/13] ./gradlew apiDump --- .../api/sentry-spring-boot-jakarta.api | 8 ++++++++ sentry-spring-boot/api/sentry-spring-boot.api | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api index ac89da6d379..1aa1010789c 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -71,6 +71,14 @@ public class io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration { public fun sentryWebExceptionHandler (Lio/sentry/IScopes;)Lio/sentry/spring/jakarta/webflux/SentryWebExceptionHandler; } +public final class io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor : io/sentry/EventProcessor { + public fun (Lorg/springframework/core/env/Environment;)V + public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; + public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; + public fun processInternal (Lio/sentry/SentryBaseEvent;)V +} + public class io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration { public fun ()V public fun exceptionResolverAdapter ()Lio/sentry/spring/jakarta/graphql/SentryDataFetcherExceptionResolverAdapter; diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index 79b72bfb39f..2e4668eb6a7 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -63,6 +63,14 @@ public class io/sentry/spring/boot/SentryWebfluxAutoConfiguration { public fun sentryWebFilter (Lio/sentry/IScopes;)Lio/sentry/spring/webflux/SentryWebFilter; } +public final class io/sentry/spring/boot/SpringProfilesEventProcessor : io/sentry/EventProcessor { + public fun (Lorg/springframework/core/env/Environment;)V + public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; + public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; + public fun processInternal (Lio/sentry/SentryBaseEvent;)V +} + public class io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration { public fun ()V public fun exceptionResolverAdapter ()Lio/sentry/spring/graphql/SentryDataFetcherExceptionResolverAdapter; From 4b79948f8d7e8b3ccf40f4d440fdd0e8120f9904 Mon Sep 17 00:00:00 2001 From: lcian Date: Tue, 11 Feb 2025 16:36:46 +0100 Subject: [PATCH 07/13] move main class and tests to the spring and spring-jakarta modules --- .../boot/jakarta/SentryAutoConfiguration.java | 1 + .../jakarta/SentryAutoConfigurationTest.kt | 1 + .../spring/boot/SentryAutoConfiguration.java | 1 + .../boot/SentryAutoConfigurationTest.kt | 1 + .../jakarta/SpringProfilesEventProcessor.java | 2 +- .../SpringProfilesEventProcessorTest.kt | 29 +++--- .../spring}/SpringProfilesEventProcessor.java | 2 +- .../SpringProfilesEventProcessorTest.kt | 91 +++++++++++++++++++ 8 files changed, 115 insertions(+), 13 deletions(-) rename {sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot => sentry-spring-jakarta/src/main/java/io/sentry/spring}/jakarta/SpringProfilesEventProcessor.java (97%) rename {sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot => sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta}/SpringProfilesEventProcessorTest.kt (76%) rename {sentry-spring-boot/src/main/java/io/sentry/spring/boot => sentry-spring/src/main/java/io/sentry/spring}/SpringProfilesEventProcessor.java (98%) create mode 100644 sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt 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 273fe84cc96..811b8449df9 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 @@ -23,6 +23,7 @@ import io.sentry.spring.jakarta.SentryUserFilter; import io.sentry.spring.jakarta.SentryUserProvider; import io.sentry.spring.jakarta.SentryWebConfiguration; +import io.sentry.spring.jakarta.SpringProfilesEventProcessor; import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider; import io.sentry.spring.jakarta.checkin.SentryCheckInAdviceConfiguration; import io.sentry.spring.jakarta.checkin.SentryCheckInPointcutConfiguration; 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 79d3d99563a..e5b4a901662 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 @@ -28,6 +28,7 @@ import io.sentry.spring.jakarta.HttpServletRequestSentryUserProvider import io.sentry.spring.jakarta.SentryExceptionResolver import io.sentry.spring.jakarta.SentryUserFilter import io.sentry.spring.jakarta.SentryUserProvider +import io.sentry.spring.jakarta.SpringProfilesEventProcessor import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider import io.sentry.spring.jakarta.tracing.SentryTracingFilter import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider 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 7710830acd4..9745c8a6e55 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 @@ -22,6 +22,7 @@ import io.sentry.spring.SentryUserFilter; import io.sentry.spring.SentryUserProvider; import io.sentry.spring.SentryWebConfiguration; +import io.sentry.spring.SpringProfilesEventProcessor; import io.sentry.spring.SpringSecuritySentryUserProvider; import io.sentry.spring.boot.graphql.SentryGraphqlAutoConfiguration; import io.sentry.spring.checkin.SentryCheckInAdviceConfiguration; 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 e7cdab9f853..b3c1effa419 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 @@ -28,6 +28,7 @@ import io.sentry.spring.HttpServletRequestSentryUserProvider import io.sentry.spring.SentryExceptionResolver import io.sentry.spring.SentryUserFilter import io.sentry.spring.SentryUserProvider +import io.sentry.spring.SpringProfilesEventProcessor import io.sentry.spring.SpringSecuritySentryUserProvider import io.sentry.spring.tracing.SentryTracingFilter import io.sentry.spring.tracing.SpringServletTransactionNameProvider diff --git a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java similarity index 97% rename from sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java rename to sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java index aeba1cec1d5..eedb9ff677b 100644 --- a/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java @@ -1,4 +1,4 @@ -package io.sentry.spring.boot.jakarta; +package io.sentry.spring.jakarta; import io.sentry.EventProcessor; import io.sentry.Hint; diff --git a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt similarity index 76% rename from sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt rename to sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt index 764889866ed..a2c96341b75 100644 --- a/sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt @@ -1,9 +1,8 @@ -package io.sentry.spring.boot +package io.sentry.spring.jakarta import io.sentry.ITransportFactory import io.sentry.Sentry import io.sentry.checkEvent -import io.sentry.spring.boot.SentryAutoConfigurationTest.MockTransportConfiguration import io.sentry.transport.ITransport import org.assertj.core.api.Assertions.assertThat import org.mockito.kotlin.any @@ -11,23 +10,22 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import org.springframework.boot.autoconfigure.AutoConfigurations -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration -import org.springframework.boot.test.context.runner.WebApplicationContextRunner import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.core.env.Environment import kotlin.test.Test class SpringProfilesEventProcessorTest { - private val contextRunner = WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) + private val contextRunner = ApplicationContextRunner() + .withUserConfiguration(AppConfiguration::class.java) + .withUserConfiguration(SpringProfilesEventProcessorConfiguration::class.java) .withUserConfiguration(MockTransportConfiguration::class.java) @Test fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .withUserConfiguration(MockTransportConfiguration::class.java) + contextRunner .run { Sentry.captureMessage("test") val transport = it.getBean(ITransport::class.java) @@ -46,8 +44,6 @@ class SpringProfilesEventProcessorTest { @Test fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { contextRunner - .withPropertyValues("sentry.dsn=http://key@localhost/proj") - .withUserConfiguration(MockTransportConfiguration::class.java) .withPropertyValues( "spring.profiles.active=test1,test2" ) @@ -66,6 +62,17 @@ class SpringProfilesEventProcessorTest { } } + @EnableSentry(dsn = "http://key@localhost/proj") + class AppConfiguration + + @Configuration(proxyBeanMethods = false) + open class SpringProfilesEventProcessorConfiguration { + @Bean + open fun springProfilesEventProcessor(environment: Environment): SpringProfilesEventProcessor { + return SpringProfilesEventProcessor(environment); + } + } + @Configuration(proxyBeanMethods = false) open class MockTransportConfiguration { diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java similarity index 98% rename from sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java rename to sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java index 5669611ab85..de941994620 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SpringProfilesEventProcessor.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java @@ -1,4 +1,4 @@ -package io.sentry.spring.boot; +package io.sentry.spring; import io.sentry.EventProcessor; import io.sentry.Hint; diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt new file mode 100644 index 00000000000..7cc7d1046c3 --- /dev/null +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt @@ -0,0 +1,91 @@ +package io.sentry.spring + +import io.sentry.ITransportFactory +import io.sentry.Sentry +import io.sentry.checkEvent +import io.sentry.transport.ITransport +import org.assertj.core.api.Assertions.assertThat +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.core.env.Environment +import kotlin.test.Test + +class SpringProfilesEventProcessorTest { + + private val contextRunner = ApplicationContextRunner() + .withUserConfiguration(AppConfiguration::class.java) + .withUserConfiguration(SpringProfilesEventProcessorConfiguration::class.java) + .withUserConfiguration(MockTransportConfiguration::class.java) + + @Test + fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { + contextRunner + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) + }, + anyOrNull() + ) + } + } + + @Test + fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { + contextRunner + .withPropertyValues( + "spring.profiles.active=test1,test2" + ) + .run { + Sentry.captureMessage("test") + val transport = it.getBean(ITransport::class.java) + verify(transport).send( + checkEvent { event -> + val traceContext = event.contexts.trace + assertThat(traceContext).isNotNull() + val traceData = traceContext!!.data + assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) + }, + anyOrNull() + ) + } + } + + @EnableSentry(dsn = "http://key@localhost/proj") + class AppConfiguration + + @Configuration(proxyBeanMethods = false) + open class SpringProfilesEventProcessorConfiguration { + @Bean + open fun springProfilesEventProcessor(environment: Environment): SpringProfilesEventProcessor { + return SpringProfilesEventProcessor(environment); + } + } + + @Configuration(proxyBeanMethods = false) + open class MockTransportConfiguration { + + private val transport = mock() + + @Bean + open fun mockTransportFactory(): ITransportFactory { + val factory = mock() + whenever(factory.create(any(), any())).thenReturn(transport) + return factory + } + + @Bean + open fun sentryTransport() = transport + } +} From 4befd052094e7bcb33f4f60c7903a70fe8a598bd Mon Sep 17 00:00:00 2001 From: lcian Date: Tue, 11 Feb 2025 16:38:09 +0100 Subject: [PATCH 08/13] ./gradlew apiDump --- .../api/sentry-spring-boot-jakarta.api | 8 -------- sentry-spring-boot/api/sentry-spring-boot.api | 8 -------- sentry-spring-jakarta/api/sentry-spring-jakarta.api | 8 ++++++++ .../spring/jakarta/SpringProfilesEventProcessorTest.kt | 4 ++-- sentry-spring/api/sentry-spring.api | 8 ++++++++ .../io/sentry/spring/SpringProfilesEventProcessorTest.kt | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api index 1aa1010789c..ac89da6d379 100644 --- a/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api +++ b/sentry-spring-boot-jakarta/api/sentry-spring-boot-jakarta.api @@ -71,14 +71,6 @@ public class io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration { public fun sentryWebExceptionHandler (Lio/sentry/IScopes;)Lio/sentry/spring/jakarta/webflux/SentryWebExceptionHandler; } -public final class io/sentry/spring/boot/jakarta/SpringProfilesEventProcessor : io/sentry/EventProcessor { - public fun (Lorg/springframework/core/env/Environment;)V - public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; - public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; - public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; - public fun processInternal (Lio/sentry/SentryBaseEvent;)V -} - public class io/sentry/spring/boot/jakarta/graphql/SentryGraphql22AutoConfiguration { public fun ()V public fun exceptionResolverAdapter ()Lio/sentry/spring/jakarta/graphql/SentryDataFetcherExceptionResolverAdapter; diff --git a/sentry-spring-boot/api/sentry-spring-boot.api b/sentry-spring-boot/api/sentry-spring-boot.api index 2e4668eb6a7..79b72bfb39f 100644 --- a/sentry-spring-boot/api/sentry-spring-boot.api +++ b/sentry-spring-boot/api/sentry-spring-boot.api @@ -63,14 +63,6 @@ public class io/sentry/spring/boot/SentryWebfluxAutoConfiguration { public fun sentryWebFilter (Lio/sentry/IScopes;)Lio/sentry/spring/webflux/SentryWebFilter; } -public final class io/sentry/spring/boot/SpringProfilesEventProcessor : io/sentry/EventProcessor { - public fun (Lorg/springframework/core/env/Environment;)V - public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; - public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; - public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; - public fun processInternal (Lio/sentry/SentryBaseEvent;)V -} - public class io/sentry/spring/boot/graphql/SentryGraphqlAutoConfiguration { public fun ()V public fun exceptionResolverAdapter ()Lio/sentry/spring/graphql/SentryDataFetcherExceptionResolverAdapter; diff --git a/sentry-spring-jakarta/api/sentry-spring-jakarta.api b/sentry-spring-jakarta/api/sentry-spring-jakarta.api index 1da9c6f03a6..7cabd51b7f4 100644 --- a/sentry-spring-jakarta/api/sentry-spring-jakarta.api +++ b/sentry-spring-jakarta/api/sentry-spring-jakarta.api @@ -86,6 +86,14 @@ public class io/sentry/spring/jakarta/SentryWebConfiguration { public fun httpServletRequestSentryUserProvider (Lio/sentry/SentryOptions;)Lio/sentry/spring/jakarta/HttpServletRequestSentryUserProvider; } +public final class io/sentry/spring/jakarta/SpringProfilesEventProcessor : io/sentry/EventProcessor { + public fun (Lorg/springframework/core/env/Environment;)V + public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; + public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; + public fun processInternal (Lio/sentry/SentryBaseEvent;)V +} + public final class io/sentry/spring/jakarta/SpringSecuritySentryUserProvider : io/sentry/spring/jakarta/SentryUserProvider { public fun (Lio/sentry/SentryOptions;)V public fun provideUser ()Lio/sentry/protocol/User; diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt index a2c96341b75..a4ed6f5bb67 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt @@ -10,9 +10,9 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.core.env.Environment import kotlin.test.Test @@ -69,7 +69,7 @@ class SpringProfilesEventProcessorTest { open class SpringProfilesEventProcessorConfiguration { @Bean open fun springProfilesEventProcessor(environment: Environment): SpringProfilesEventProcessor { - return SpringProfilesEventProcessor(environment); + return SpringProfilesEventProcessor(environment) } } diff --git a/sentry-spring/api/sentry-spring.api b/sentry-spring/api/sentry-spring.api index ebc918f0779..2222d681d3f 100644 --- a/sentry-spring/api/sentry-spring.api +++ b/sentry-spring/api/sentry-spring.api @@ -86,6 +86,14 @@ public class io/sentry/spring/SentryWebConfiguration { public fun httpServletRequestSentryUserProvider (Lio/sentry/SentryOptions;)Lio/sentry/spring/HttpServletRequestSentryUserProvider; } +public final class io/sentry/spring/SpringProfilesEventProcessor : io/sentry/EventProcessor { + public fun (Lorg/springframework/core/env/Environment;)V + public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; + public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; + public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; + public fun processInternal (Lio/sentry/SentryBaseEvent;)V +} + public final class io/sentry/spring/SpringSecuritySentryUserProvider : io/sentry/spring/SentryUserProvider { public fun (Lio/sentry/SentryOptions;)V public fun provideUser ()Lio/sentry/protocol/User; diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt index 7cc7d1046c3..ce18d6f16e9 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt @@ -10,9 +10,9 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.boot.test.context.runner.ApplicationContextRunner import org.springframework.core.env.Environment import kotlin.test.Test @@ -69,7 +69,7 @@ class SpringProfilesEventProcessorTest { open class SpringProfilesEventProcessorConfiguration { @Bean open fun springProfilesEventProcessor(environment: Environment): SpringProfilesEventProcessor { - return SpringProfilesEventProcessor(environment); + return SpringProfilesEventProcessor(environment) } } From 49de0d75fa4f19a1bdad81f53349fece5af0ceb6 Mon Sep 17 00:00:00 2001 From: lcian Date: Tue, 11 Feb 2025 16:42:48 +0100 Subject: [PATCH 09/13] delete duplicate test in spring-boot-jakarta --- .../SpringProfilesEventProcessorTest.kt | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt diff --git a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt deleted file mode 100644 index 9fc1d0af589..00000000000 --- a/sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SpringProfilesEventProcessorTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.sentry.spring.boot.jakarta - -import io.sentry.ITransportFactory -import io.sentry.Sentry -import io.sentry.checkEvent -import io.sentry.transport.ITransport -import org.assertj.core.api.Assertions.assertThat -import org.mockito.kotlin.any -import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever -import org.springframework.boot.autoconfigure.AutoConfigurations -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration -import org.springframework.boot.test.context.runner.WebApplicationContextRunner -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import kotlin.test.Test - -class SpringProfilesEventProcessorTest { - - private val contextRunner = WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SentryAutoConfiguration::class.java, WebMvcAutoConfiguration::class.java)) - .withUserConfiguration(MockTransportConfiguration::class.java) - - @Test - fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { - contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj") - .withUserConfiguration(MockTransportConfiguration::class.java) - .run { - Sentry.captureMessage("test") - val transport = it.getBean(ITransport::class.java) - verify(transport).send( - checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) - }, - anyOrNull() - ) - } - } - - @Test - fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { - contextRunner - .withPropertyValues("sentry.dsn=http://key@localhost/proj") - .withUserConfiguration(MockTransportConfiguration::class.java) - .withPropertyValues( - "spring.profiles.active=test1,test2" - ) - .run { - Sentry.captureMessage("test") - val transport = it.getBean(ITransport::class.java) - verify(transport).send( - checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) - }, - anyOrNull() - ) - } - } - - @Configuration(proxyBeanMethods = false) - open class MockTransportConfiguration { - - private val transport = mock() - - @Bean - open fun mockTransportFactory(): ITransportFactory { - val factory = mock() - whenever(factory.create(any(), any())).thenReturn(transport) - return factory - } - - @Bean - open fun sentryTransport() = transport - } -} From 0a95314844e0412bb532e62a70878ef6642aa988 Mon Sep 17 00:00:00 2001 From: lcian Date: Mon, 17 Feb 2025 20:51:53 +0100 Subject: [PATCH 10/13] use a new Context for Spring, add tests --- .../api/sentry-spring-jakarta.api | 1 - .../jakarta/SpringProfilesEventProcessor.java | 18 +-- .../SpringProfilesEventProcessorTest.kt | 19 ++- sentry-spring/api/sentry-spring.api | 1 - .../spring/SpringProfilesEventProcessor.java | 18 +-- .../SpringProfilesEventProcessorTest.kt | 19 ++- sentry/api/sentry.api | 28 ++++ .../java/io/sentry/CombinedContextsView.java | 19 +++ .../java/io/sentry/protocol/Contexts.java | 13 ++ .../main/java/io/sentry/protocol/Spring.java | 122 ++++++++++++++++++ .../io/sentry/CombinedContextsViewTest.kt | 39 ++++++ .../CombinedContextsViewSerializationTest.kt | 1 + .../protocol/ContextsSerializationTest.kt | 1 + .../java/io/sentry/protocol/ContextsTest.kt | 2 + .../protocol/SentryEventSerializationTest.kt | 1 + .../protocol/SpringSerializationTest.kt | 39 ++++++ sentry/src/test/resources/json/contexts.json | 4 + .../src/test/resources/json/sentry_event.json | 4 + sentry/src/test/resources/json/spring.json | 3 + 19 files changed, 306 insertions(+), 46 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/protocol/Spring.java create mode 100644 sentry/src/test/java/io/sentry/protocol/SpringSerializationTest.kt create mode 100644 sentry/src/test/resources/json/spring.json diff --git a/sentry-spring-jakarta/api/sentry-spring-jakarta.api b/sentry-spring-jakarta/api/sentry-spring-jakarta.api index 7cabd51b7f4..df817d01e51 100644 --- a/sentry-spring-jakarta/api/sentry-spring-jakarta.api +++ b/sentry-spring-jakarta/api/sentry-spring-jakarta.api @@ -91,7 +91,6 @@ public final class io/sentry/spring/jakarta/SpringProfilesEventProcessor : io/se public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; - public fun processInternal (Lio/sentry/SentryBaseEvent;)V } public final class io/sentry/spring/jakarta/SpringSecuritySentryUserProvider : io/sentry/spring/jakarta/SentryUserProvider { diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java index eedb9ff677b..48957e88507 100644 --- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/SpringProfilesEventProcessor.java @@ -5,8 +5,8 @@ import io.sentry.SentryBaseEvent; import io.sentry.SentryEvent; import io.sentry.SentryReplayEvent; -import io.sentry.SpanContext; import io.sentry.protocol.SentryTransaction; +import io.sentry.protocol.Spring; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.core.env.Environment; @@ -16,8 +16,6 @@ * to the {@link io.sentry.TraceContext} associated with the event. */ public final class SpringProfilesEventProcessor implements EventProcessor { - private static final @NotNull String ACTIVE_PROFILES_TRACE_CONTEXT_KEY = "spring.active_profiles"; - private final @NotNull Environment environment; @Override @@ -40,15 +38,11 @@ public final class SpringProfilesEventProcessor implements EventProcessor { return event; } - public void processInternal(final @NotNull SentryBaseEvent event) { - @Nullable SpanContext trace = event.getContexts().getTrace(); - if (trace != null) { - @Nullable String[] activeProfiles = environment.getActiveProfiles(); - if (activeProfiles == null) { - activeProfiles = new String[0]; - } - trace.setData(ACTIVE_PROFILES_TRACE_CONTEXT_KEY, activeProfiles); - } + private void processInternal(final @NotNull SentryBaseEvent event) { + @Nullable String[] activeProfiles = environment.getActiveProfiles(); + @NotNull Spring springContext = new Spring(); + springContext.setActiveProfiles(activeProfiles); + event.getContexts().setSpring(springContext); } public SpringProfilesEventProcessor(final @NotNull Environment environment) { diff --git a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt index a4ed6f5bb67..24a5a1bdd1c 100644 --- a/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/SpringProfilesEventProcessorTest.kt @@ -3,6 +3,7 @@ package io.sentry.spring.jakarta import io.sentry.ITransportFactory import io.sentry.Sentry import io.sentry.checkEvent +import io.sentry.protocol.Spring import io.sentry.transport.ITransport import org.assertj.core.api.Assertions.assertThat import org.mockito.kotlin.any @@ -24,17 +25,16 @@ class SpringProfilesEventProcessorTest { .withUserConfiguration(MockTransportConfiguration::class.java) @Test - fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { + fun `when default Spring profile is active, sets active_profiles in Spring context to empty list on sent event`() { contextRunner .run { Sentry.captureMessage("test") val transport = it.getBean(ITransport::class.java) verify(transport).send( checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) + val expected = Spring() + expected.activeProfiles = listOf().toTypedArray() + assertThat(event.contexts.spring).isEqualTo(expected) }, anyOrNull() ) @@ -42,7 +42,7 @@ class SpringProfilesEventProcessorTest { } @Test - fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { + fun `when non-default Spring profiles are active, sets active profiles in Spring context to list of profile names`() { contextRunner .withPropertyValues( "spring.profiles.active=test1,test2" @@ -52,10 +52,9 @@ class SpringProfilesEventProcessorTest { val transport = it.getBean(ITransport::class.java) verify(transport).send( checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) + val expected = Spring() + expected.activeProfiles = listOf("test1", "test2").toTypedArray() + assertThat(event.contexts.spring).isEqualTo(expected) }, anyOrNull() ) diff --git a/sentry-spring/api/sentry-spring.api b/sentry-spring/api/sentry-spring.api index 2222d681d3f..67ad0c1c963 100644 --- a/sentry-spring/api/sentry-spring.api +++ b/sentry-spring/api/sentry-spring.api @@ -91,7 +91,6 @@ public final class io/sentry/spring/SpringProfilesEventProcessor : io/sentry/Eve public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; public fun process (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/SentryReplayEvent; public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; - public fun processInternal (Lio/sentry/SentryBaseEvent;)V } public final class io/sentry/spring/SpringSecuritySentryUserProvider : io/sentry/spring/SentryUserProvider { diff --git a/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java b/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java index de941994620..30b6483fd31 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java +++ b/sentry-spring/src/main/java/io/sentry/spring/SpringProfilesEventProcessor.java @@ -5,8 +5,8 @@ import io.sentry.SentryBaseEvent; import io.sentry.SentryEvent; import io.sentry.SentryReplayEvent; -import io.sentry.SpanContext; import io.sentry.protocol.SentryTransaction; +import io.sentry.protocol.Spring; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.core.env.Environment; @@ -16,8 +16,6 @@ * to the {@link io.sentry.TraceContext} associated with the event. */ public final class SpringProfilesEventProcessor implements EventProcessor { - private static final @NotNull String ACTIVE_PROFILES_TRACE_CONTEXT_KEY = "spring.active_profiles"; - private final @NotNull Environment environment; @Override @@ -40,15 +38,11 @@ public final class SpringProfilesEventProcessor implements EventProcessor { return event; } - public void processInternal(final @NotNull SentryBaseEvent event) { - @Nullable SpanContext trace = event.getContexts().getTrace(); - if (trace != null) { - @Nullable String[] activeProfiles = environment.getActiveProfiles(); - if (activeProfiles == null) { - activeProfiles = new String[0]; - } - trace.setData(ACTIVE_PROFILES_TRACE_CONTEXT_KEY, activeProfiles); - } + private void processInternal(final @NotNull SentryBaseEvent event) { + @Nullable String[] activeProfiles = environment.getActiveProfiles(); + @NotNull Spring springContext = new Spring(); + springContext.setActiveProfiles(activeProfiles); + event.getContexts().setSpring(springContext); } public SpringProfilesEventProcessor(final @NotNull Environment environment) { diff --git a/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt index ce18d6f16e9..ed81ec03331 100644 --- a/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt +++ b/sentry-spring/src/test/kotlin/io/sentry/spring/SpringProfilesEventProcessorTest.kt @@ -3,6 +3,7 @@ package io.sentry.spring import io.sentry.ITransportFactory import io.sentry.Sentry import io.sentry.checkEvent +import io.sentry.protocol.Spring import io.sentry.transport.ITransport import org.assertj.core.api.Assertions.assertThat import org.mockito.kotlin.any @@ -24,17 +25,16 @@ class SpringProfilesEventProcessorTest { .withUserConfiguration(MockTransportConfiguration::class.java) @Test - fun `when default Spring profile is active, sets traceContext spring active_profiles to empty list on sent event`() { + fun `when default Spring profile is active, sets active_profiles in Spring context to empty list on sent event`() { contextRunner .run { Sentry.captureMessage("test") val transport = it.getBean(ITransport::class.java) verify(transport).send( checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf()) + val expected = Spring() + expected.activeProfiles = listOf().toTypedArray() + assertThat(event.contexts.spring).isEqualTo(expected) }, anyOrNull() ) @@ -42,7 +42,7 @@ class SpringProfilesEventProcessorTest { } @Test - fun `when non-default Spring profiles are active, sets traceContext spring active_profiles to array of profile names`() { + fun `when non-default Spring profiles are active, sets active profiles in Spring context to list of profile names`() { contextRunner .withPropertyValues( "spring.profiles.active=test1,test2" @@ -52,10 +52,9 @@ class SpringProfilesEventProcessorTest { val transport = it.getBean(ITransport::class.java) verify(transport).send( checkEvent { event -> - val traceContext = event.contexts.trace - assertThat(traceContext).isNotNull() - val traceData = traceContext!!.data - assertThat(traceData.get("spring.active_profiles")).isEqualTo(listOf("test1", "test2")) + val expected = Spring() + expected.activeProfiles = listOf("test1", "test2").toTypedArray() + assertThat(event.contexts.spring).isEqualTo(expected) }, anyOrNull() ) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 662d87c2650..09b264aa007 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -225,6 +225,7 @@ public final class io/sentry/CombinedContextsView : io/sentry/protocol/Contexts public fun getResponse ()Lio/sentry/protocol/Response; public fun getRuntime ()Lio/sentry/protocol/SentryRuntime; public fun getSize ()I + public fun getSpring ()Lio/sentry/protocol/Spring; public fun getTrace ()Lio/sentry/SpanContext; public fun isEmpty ()Z public fun keys ()Ljava/util/Enumeration; @@ -238,6 +239,7 @@ public final class io/sentry/CombinedContextsView : io/sentry/protocol/Contexts public fun setOperatingSystem (Lio/sentry/protocol/OperatingSystem;)V public fun setResponse (Lio/sentry/protocol/Response;)V public fun setRuntime (Lio/sentry/protocol/SentryRuntime;)V + public fun setSpring (Lio/sentry/protocol/Spring;)V public fun setTrace (Lio/sentry/SpanContext;)V public fun size ()I public fun withResponse (Lio/sentry/util/HintUtils$SentryConsumer;)V @@ -4422,6 +4424,7 @@ public class io/sentry/protocol/Contexts : io/sentry/JsonSerializable { public fun getResponse ()Lio/sentry/protocol/Response; public fun getRuntime ()Lio/sentry/protocol/SentryRuntime; public fun getSize ()I + public fun getSpring ()Lio/sentry/protocol/Spring; public fun getTrace ()Lio/sentry/SpanContext; public fun hashCode ()I public fun isEmpty ()Z @@ -4439,6 +4442,7 @@ public class io/sentry/protocol/Contexts : io/sentry/JsonSerializable { public fun setOperatingSystem (Lio/sentry/protocol/OperatingSystem;)V public fun setResponse (Lio/sentry/protocol/Response;)V public fun setRuntime (Lio/sentry/protocol/SentryRuntime;)V + public fun setSpring (Lio/sentry/protocol/Spring;)V public fun setTrace (Lio/sentry/SpanContext;)V public fun size ()I public fun withResponse (Lio/sentry/util/HintUtils$SentryConsumer;)V @@ -5352,6 +5356,30 @@ public final class io/sentry/protocol/SentryTransaction$JsonKeys { public fun ()V } +public final class io/sentry/protocol/Spring : io/sentry/JsonSerializable, io/sentry/JsonUnknown { + public static final field TYPE Ljava/lang/String; + public fun ()V + public fun (Lio/sentry/protocol/Spring;)V + public fun equals (Ljava/lang/Object;)Z + public fun getActiveProfiles ()[Ljava/lang/String; + public fun getUnknown ()Ljava/util/Map; + public fun hashCode ()I + public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V + public fun setActiveProfiles ([Ljava/lang/String;)V + public fun setUnknown (Ljava/util/Map;)V +} + +public final class io/sentry/protocol/Spring$Deserializer : io/sentry/JsonDeserializer { + public fun ()V + public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/protocol/Spring; + public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; +} + +public final class io/sentry/protocol/Spring$JsonKeys { + public static final field ACTIVE_PROFILES Ljava/lang/String; + public fun ()V +} + public final class io/sentry/protocol/TransactionInfo : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun (Ljava/lang/String;)V public fun getUnknown ()Ljava/util/Map; diff --git a/sentry/src/main/java/io/sentry/CombinedContextsView.java b/sentry/src/main/java/io/sentry/CombinedContextsView.java index b5445784c03..11e459877d6 100644 --- a/sentry/src/main/java/io/sentry/CombinedContextsView.java +++ b/sentry/src/main/java/io/sentry/CombinedContextsView.java @@ -8,6 +8,7 @@ import io.sentry.protocol.OperatingSystem; import io.sentry.protocol.Response; import io.sentry.protocol.SentryRuntime; +import io.sentry.protocol.Spring; import io.sentry.util.HintUtils; import java.io.IOException; import java.util.Enumeration; @@ -206,6 +207,24 @@ public void setResponse(@NotNull Response response) { getDefaultContexts().setResponse(response); } + @Override + public @Nullable Spring getSpring() { + final @Nullable Spring current = currentContexts.getSpring(); + if (current != null) { + return current; + } + final @Nullable Spring isolation = isolationContexts.getSpring(); + if (isolation != null) { + return isolation; + } + return globalContexts.getSpring(); + } + + @Override + public void setSpring(@NotNull Spring spring) { + getDefaultContexts().setSpring(spring); + } + @Override public int size() { return mergeContexts().size(); diff --git a/sentry/src/main/java/io/sentry/protocol/Contexts.java b/sentry/src/main/java/io/sentry/protocol/Contexts.java index 53c97bcb0b0..3705452399c 100644 --- a/sentry/src/main/java/io/sentry/protocol/Contexts.java +++ b/sentry/src/main/java/io/sentry/protocol/Contexts.java @@ -56,6 +56,8 @@ public Contexts(final @NotNull Contexts contexts) { this.setTrace(new SpanContext((SpanContext) value)); } else if (Response.TYPE.equals(entry.getKey()) && value instanceof Response) { this.setResponse(new Response((Response) value)); + } else if (Spring.TYPE.equals(entry.getKey()) && value instanceof Spring) { + this.setSpring(new Spring((Spring) value)); } else { this.put(entry.getKey(), value); } @@ -148,6 +150,14 @@ public void setResponse(final @NotNull Response response) { } } + public @Nullable Spring getSpring() { + return toContextType(Spring.TYPE, Spring.class); + } + + public void setSpring(final @NotNull Spring spring) { + this.put(Spring.TYPE, spring); + } + public int size() { // since this used to extend map return internalStorage.size(); @@ -266,6 +276,9 @@ public static final class Deserializer implements JsonDeserializer { case Response.TYPE: contexts.setResponse(new Response.Deserializer().deserialize(reader, logger)); break; + case Spring.TYPE: + contexts.setSpring(new Spring.Deserializer().deserialize(reader, logger)); + break; default: Object object = reader.nextObjectOrNull(); if (object != null) { diff --git a/sentry/src/main/java/io/sentry/protocol/Spring.java b/sentry/src/main/java/io/sentry/protocol/Spring.java new file mode 100644 index 00000000000..849b05168e3 --- /dev/null +++ b/sentry/src/main/java/io/sentry/protocol/Spring.java @@ -0,0 +1,122 @@ +package io.sentry.protocol; + +import io.sentry.ILogger; +import io.sentry.JsonDeserializer; +import io.sentry.JsonSerializable; +import io.sentry.JsonUnknown; +import io.sentry.ObjectReader; +import io.sentry.ObjectWriter; +import io.sentry.util.CollectionUtils; +import io.sentry.vendor.gson.stream.JsonToken; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class Spring implements JsonUnknown, JsonSerializable { + public static final String TYPE = "spring"; + + /** + * The names of the active Spring profiles. Is an empty list if the only active profile is the + * default one. + */ + private @Nullable String[] activeProfiles; + + /** Unknown fields, only for internal usage. */ + private @Nullable Map unknown; + + public Spring() {} + + public Spring(final @NotNull Spring spring) { + this.activeProfiles = spring.activeProfiles; + this.unknown = CollectionUtils.newConcurrentHashMap(spring.unknown); + } + + public @Nullable String[] getActiveProfiles() { + return activeProfiles; + } + + public void setActiveProfiles(final @Nullable String[] activeProfiles) { + this.activeProfiles = activeProfiles; + } + + @Nullable + @Override + public Map getUnknown() { + return unknown; + } + + @Override + public void setUnknown(@Nullable Map unknown) { + this.unknown = unknown; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Spring spring = (Spring) o; + return Arrays.equals(activeProfiles, spring.activeProfiles); + } + + @Override + public int hashCode() { + return Arrays.hashCode(activeProfiles); + } + + public static final class JsonKeys { + public static final String ACTIVE_PROFILES = "active_profiles"; + } + + @Override + public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) + throws IOException { + writer.beginObject(); + if (activeProfiles != null) { + writer.name(JsonKeys.ACTIVE_PROFILES).value(logger, activeProfiles); + } + if (unknown != null) { + for (String key : unknown.keySet()) { + Object value = unknown.get(key); + writer.name(key); + writer.value(logger, value); + } + } + writer.endObject(); + } + + public static final class Deserializer implements JsonDeserializer { + @Override + public @NotNull Spring deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) + throws Exception { + reader.beginObject(); + Spring spring = new Spring(); + Map unknown = null; + while (reader.peek() == JsonToken.NAME) { + final String nextName = reader.nextName(); + switch (nextName) { + case JsonKeys.ACTIVE_PROFILES: + List activeProfilesList = (List) reader.nextObjectOrNull(); + if (activeProfilesList != null) { + Object[] activeProfiles = new String[activeProfilesList.size()]; + activeProfilesList.toArray(activeProfiles); + spring.activeProfiles = (String[]) activeProfiles; + } + break; + default: + if (unknown == null) { + unknown = new ConcurrentHashMap<>(); + } + reader.nextUnknown(logger, unknown, nextName); + break; + } + } + spring.setUnknown(unknown); + reader.endObject(); + return spring; + } + } +} diff --git a/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt b/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt index b70e9506a8f..2d8d04f22f9 100644 --- a/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt +++ b/sentry/src/test/java/io/sentry/CombinedContextsViewTest.kt @@ -8,7 +8,9 @@ import io.sentry.protocol.Gpu import io.sentry.protocol.OperatingSystem import io.sentry.protocol.Response import io.sentry.protocol.SentryRuntime +import io.sentry.protocol.Spring import kotlin.test.Test +import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull @@ -411,6 +413,43 @@ class CombinedContextsViewTest { assertNull(fixture.global.response) } + @Test + fun `prefers spring from current context`() { + val combined = fixture.getSut() + fixture.current.setSpring(Spring().also { it.activeProfiles = listOf("current").toTypedArray() }) + fixture.isolation.setSpring(Spring().also { it.activeProfiles = listOf("isolation").toTypedArray() }) + fixture.global.setSpring(Spring().also { it.activeProfiles = listOf("global").toTypedArray() }) + + assertContentEquals(listOf("current").toTypedArray(), combined.spring?.activeProfiles) + } + + @Test + fun `uses isolation spring if current context does not have it`() { + val combined = fixture.getSut() + fixture.isolation.setSpring(Spring().also { it.activeProfiles = listOf("isolation").toTypedArray() }) + fixture.global.setSpring(Spring().also { it.activeProfiles = listOf("global").toTypedArray() }) + + assertContentEquals(listOf("isolation").toTypedArray(), combined.spring?.activeProfiles) + } + + @Test + fun `uses global spring if current and isolation context do not have it`() { + val combined = fixture.getSut() + fixture.global.setSpring(Spring().also { it.activeProfiles = listOf("global").toTypedArray() }) + + assertContentEquals(listOf("global").toTypedArray(), combined.spring?.activeProfiles) + } + + @Test + fun `sets spring on default context`() { + val combined = fixture.getSut() + combined.setSpring(Spring().also { it.activeProfiles = listOf("test").toTypedArray() }) + + assertNull(fixture.current.spring) + assertContentEquals(listOf("test").toTypedArray(), combined.spring?.activeProfiles) + assertNull(fixture.global.spring) + } + @Test fun `size combines contexts`() { val combined = fixture.getSut() diff --git a/sentry/src/test/java/io/sentry/protocol/CombinedContextsViewSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/CombinedContextsViewSerializationTest.kt index cafcbb8884b..f5e92713eb4 100644 --- a/sentry/src/test/java/io/sentry/protocol/CombinedContextsViewSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/CombinedContextsViewSerializationTest.kt @@ -29,6 +29,7 @@ class CombinedContextsViewSerializationTest { isolation.setDevice(DeviceSerializationTest.Fixture().getSut()) isolation.setOperatingSystem(OperatingSystemSerializationTest.Fixture().getSut()) isolation.setResponse(ResponseSerializationTest.Fixture().getSut()) + isolation.setSpring(SpringSerializationTest.Fixture().getSut()) global.setRuntime(SentryRuntimeSerializationTest.Fixture().getSut()) global.setGpu(GpuSerializationTest.Fixture().getSut()) diff --git a/sentry/src/test/java/io/sentry/protocol/ContextsSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/ContextsSerializationTest.kt index 5c9aeb1d375..5ea43e377c5 100644 --- a/sentry/src/test/java/io/sentry/protocol/ContextsSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/ContextsSerializationTest.kt @@ -23,6 +23,7 @@ class ContextsSerializationTest { setGpu(GpuSerializationTest.Fixture().getSut()) setResponse(ResponseSerializationTest.Fixture().getSut()) setTrace(SpanContextSerializationTest.Fixture().getSut()) + setSpring(SpringSerializationTest.Fixture().getSut()) } } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt index 1d0573741fe..1b422ed9af6 100644 --- a/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/ContextsTest.kt @@ -19,6 +19,7 @@ class ContextsTest { contexts.setGpu(Gpu()) contexts.setResponse(Response()) contexts.setTrace(SpanContext("op")) + contexts.setSpring(Spring()) val clone = Contexts(contexts) @@ -32,6 +33,7 @@ class ContextsTest { assertNotSame(contexts.gpu, clone.gpu) assertNotSame(contexts.trace, clone.trace) assertNotSame(contexts.response, clone.response) + assertNotSame(contexts.spring, clone.spring) } @Test diff --git a/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt index d51ac1dc137..9edc25dd0ac 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryEventSerializationTest.kt @@ -40,6 +40,7 @@ class SentryEventSerializationTest { level = SentryLevel.ERROR transaction = "e7aea178-e3a6-46bc-be17-38a3ea8920b6" setModule("01c8a4f6-8861-4575-a10e-5ed3fba7c794", "b4083431-47e9-433a-b58f-58796f63e27c") + contexts.apply { setSpring(SpringSerializationTest.Fixture().getSut()) } SentryBaseEventSerializationTest.Fixture().update(this) } } diff --git a/sentry/src/test/java/io/sentry/protocol/SpringSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SpringSerializationTest.kt new file mode 100644 index 00000000000..59c6a021809 --- /dev/null +++ b/sentry/src/test/java/io/sentry/protocol/SpringSerializationTest.kt @@ -0,0 +1,39 @@ +package io.sentry.protocol + +import io.sentry.ILogger +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertEquals + +class SpringSerializationTest { + + class Fixture { + val logger = mock() + + fun getSut() = Spring().apply { + activeProfiles = arrayOf("some", "profiles") + } + } + private val fixture = Fixture() + + @Test + fun serialize() { + val expected = SerializationUtils.sanitizedFile("json/spring.json") + val actual = SerializationUtils.serializeToString(fixture.getSut(), fixture.logger) + + assertEquals(expected, actual) + } + + @Test + fun deserialize() { + val expectedJson = SerializationUtils.sanitizedFile("json/spring.json") + val actual = SerializationUtils.deserializeJson( + expectedJson, + Spring.Deserializer(), + fixture.logger + ) + val actualJson = SerializationUtils.serializeToString(actual, fixture.logger) + + assertEquals(expectedJson, actualJson) + } +} diff --git a/sentry/src/test/resources/json/contexts.json b/sentry/src/test/resources/json/contexts.json index a6f35b31a66..c1e97cd9dd3 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -106,6 +106,10 @@ "version": "16534f6b-1670-4bb8-aec2-647a1b97669b", "raw_description": "773b5b05-a0f9-4ee6-9f3b-13155c37ad6e" }, + "spring": + { + "active_profiles": ["some", "profiles"] + }, "trace": { "trace_id": "afcb46b1140ade5187c4bbb5daa804df", diff --git a/sentry/src/test/resources/json/sentry_event.json b/sentry/src/test/resources/json/sentry_event.json index 6d421fc9936..e250b1c95d2 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -244,6 +244,10 @@ "version": "16534f6b-1670-4bb8-aec2-647a1b97669b", "raw_description": "773b5b05-a0f9-4ee6-9f3b-13155c37ad6e" }, + "spring": + { + "active_profiles": ["some", "profiles"] + }, "trace": { "trace_id": "afcb46b1140ade5187c4bbb5daa804df", diff --git a/sentry/src/test/resources/json/spring.json b/sentry/src/test/resources/json/spring.json new file mode 100644 index 00000000000..f89ed5af58e --- /dev/null +++ b/sentry/src/test/resources/json/spring.json @@ -0,0 +1,3 @@ +{ + "active_profiles": ["some", "profiles"] +} From 607b074ec548485a3fa10fd3a2f9ee63d854c3a3 Mon Sep 17 00:00:00 2001 From: lcian Date: Mon, 17 Feb 2025 20:54:29 +0100 Subject: [PATCH 11/13] changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d33faaf5dc..e5dc0950bdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- A list of active Spring profiles is attached to the trace context and displayed in the Sentry UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) + ### Fixes - `SentryOptions.setTracePropagationTargets` is no longer marked internal ([#4170](https://github.com/getsentry/sentry-java/pull/4170)) @@ -20,7 +24,6 @@ ### Features -- A list of active Spring profiles is attached to the trace context and displayed in the Sentry UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) - This consists of an empty list when only the default profile is active - Create onCreate and onStart spans for all Activities ([#4025](https://github.com/getsentry/sentry-java/pull/4025)) - Add split apks info to the `App` context ([#3193](https://github.com/getsentry/sentry-java/pull/3193)) From 4160f0318f8e43aebb148a5c934cd261d7f8bd87 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Thu, 20 Feb 2025 09:15:59 +0100 Subject: [PATCH 12/13] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5dc0950bdb..a32e99bb3a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - A list of active Spring profiles is attached to the trace context and displayed in the Sentry UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) + - This consists of an empty list when only the default profile is active ### Fixes @@ -24,7 +25,6 @@ ### Features - - This consists of an empty list when only the default profile is active - Create onCreate and onStart spans for all Activities ([#4025](https://github.com/getsentry/sentry-java/pull/4025)) - Add split apks info to the `App` context ([#3193](https://github.com/getsentry/sentry-java/pull/3193)) - Expose new `withSentryObservableEffect` method overload that accepts `SentryNavigationListener` as a parameter ([#4143](https://github.com/getsentry/sentry-java/pull/4143)) From 1976c0566946501c14ed3c18edc0ea851cfcba68 Mon Sep 17 00:00:00 2001 From: Lorenzo Cian Date: Thu, 20 Feb 2025 09:18:55 +0100 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b379a2f27e..e9babf5adab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Features - The `ignoredErrors` option is now configurable via the manifest property `io.sentry.traces.ignored-errors` ([#4178](https://github.com/getsentry/sentry-java/pull/4178)) -- A list of active Spring profiles is attached to the trace context and displayed in the Sentry UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) +- A list of active Spring profiles is attached to payloads sent to Sentry (errors, traces, etc.) and displayed in the UI when using our Spring or Spring Boot integrations ([#4147](https://github.com/getsentry/sentry-java/pull/4147)) - This consists of an empty list when only the default profile is active ### Fixes