Skip to content
Draft
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Features

- Monitor every Spring `@Scheduled` method with Sentry Crons by setting `sentry.monitor-scheduled-tasks=true`, without adding `@SentryCheckIn` to each method. Exclude methods with `sentry.exclude-scheduled-tasks` ([#6275](https://github.com/getsentry/sentry-java/pull/6275))
- Monitor every Spring `@Scheduled` method with Sentry Crons by setting `sentry.monitor-scheduled-tasks=true`, without adding `@SentryCheckIn` to each method. Exclude methods with `sentry.exclude-scheduled-tasks` ([#6275](https://github.com/getsentry/sentry-java/pull/6275), [#6276](https://github.com/getsentry/sentry-java/pull/6276))
- Send a monitor config derived from Spring `@Scheduled` with `@SentryCheckIn` check-ins by default, so Sentry creates or updates the monitor from code. Set `@SentryCheckIn(upsertMonitorConfig = false)` to turn this off ([#6215](https://github.com/getsentry/sentry-java/pull/6215))
- Added `Environment` to the `Scope`, allowing you to overwrite the `Environment` set on the options ([#5772](https://github.com/getsentry/sentry-java/pull/5772))
- The scope `Environment` is also applied to the trace envelope header (DSC), logs, metrics and profiles, so all telemetry of a trace reports the same environment
Expand Down
4 changes: 4 additions & 0 deletions sentry-spring-boot/api/sentry-spring-boot.api
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,20 @@ public class io/sentry/spring/boot/SentryProperties : io/sentry/SentryOptions {
public fun <init> ()V
public fun getClients ()Lio/sentry/spring/boot/SentryProperties$Clients;
public fun getExceptionResolverOrder ()I
public fun getExcludeScheduledTasks ()Ljava/util/List;
public fun getGraphql ()Lio/sentry/spring/boot/SentryProperties$Graphql;
public fun getLogging ()Lio/sentry/spring/boot/SentryProperties$Logging;
public fun getUserFilterOrder ()Ljava/lang/Integer;
public fun isKeepTransactionsOpenForAsyncResponses ()Z
public fun isMonitorScheduledTasks ()Z
public fun isUseGitCommitIdAsRelease ()Z
public fun setClients (Lio/sentry/spring/boot/SentryProperties$Clients;)V
public fun setExceptionResolverOrder (I)V
public fun setExcludeScheduledTasks (Ljava/util/List;)V
public fun setGraphql (Lio/sentry/spring/boot/SentryProperties$Graphql;)V
public fun setKeepTransactionsOpenForAsyncResponses (Z)V
public fun setLogging (Lio/sentry/spring/boot/SentryProperties$Logging;)V
public fun setMonitorScheduledTasks (Z)V
public fun setUseGitCommitIdAsRelease (Z)V
public fun setUserFilterOrder (Ljava/lang/Integer;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
import io.sentry.spring.SpringSecuritySentryUserProvider;
import io.sentry.spring.boot.graphql.SentryGraphqlAutoConfiguration;
import io.sentry.spring.cache.SentryCacheBeanPostProcessor;
import io.sentry.spring.checkin.SentryCheckInAdvice;
import io.sentry.spring.checkin.SentryCheckInAdviceConfiguration;
import io.sentry.spring.checkin.SentryCheckInPointcutConfiguration;
import io.sentry.spring.checkin.SentryQuartzConfiguration;
import io.sentry.spring.checkin.SentryScheduledCheckInPointcut;
import io.sentry.spring.exception.SentryCaptureExceptionParameterPointcutConfiguration;
import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration;
import io.sentry.spring.kafka.SentryKafkaConsumerBeanPostProcessor;
Expand All @@ -47,11 +49,16 @@
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.jetbrains.annotations.NotNull;
import org.quartz.core.QuartzScheduler;
import org.slf4j.MDC;
import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
Expand All @@ -70,6 +77,7 @@
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
Expand Down Expand Up @@ -292,6 +300,27 @@ static class SentryCheckInAspectsConfiguration {
@Import(SentryCheckInPointcutConfiguration.class)
@Open
static class SentryCheckInPointcutAutoConfiguration {}

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "sentry.monitor-scheduled-tasks", havingValue = "true")
@Open
static class SentryScheduledCheckInConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public @NotNull Advice sentryScheduledCheckInAdvice() {
return new SentryCheckInAdvice(ScopesAdapter.getInstance(), true);
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public @NotNull Advisor sentryScheduledCheckInAdvisor(
final @NotNull @Qualifier("sentryScheduledCheckInAdvice") Advice advice,
final @NotNull SentryProperties sentryProperties) {
return new DefaultPointcutAdvisor(
new SentryScheduledCheckInPointcut(sentryProperties.getExcludeScheduledTasks()),
advice);
}
}
}

/** Registers beans specific to Spring MVC. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ public class SentryProperties extends SentryOptions {
/** Clients integration properties. */
private @NotNull Clients clients = new Clients();

/** Monitor every {@code @Scheduled} method with Sentry Crons. */
private boolean monitorScheduledTasks = false;

/**
* {@code @Scheduled} methods not to monitor when {@code monitorScheduledTasks} is on, as {@code
* ClassName.methodName} or a regex matching it.
*/
private @NotNull List<String> excludeScheduledTasks = new ArrayList<>();

public boolean isUseGitCommitIdAsRelease() {
return useGitCommitIdAsRelease;
}
Expand Down Expand Up @@ -109,6 +118,22 @@ public void setClients(@NotNull Clients clients) {
this.clients = clients;
}

public boolean isMonitorScheduledTasks() {
return monitorScheduledTasks;
}

public void setMonitorScheduledTasks(boolean monitorScheduledTasks) {
this.monitorScheduledTasks = monitorScheduledTasks;
}

public @NotNull List<String> getExcludeScheduledTasks() {
return excludeScheduledTasks;
}

public void setExcludeScheduledTasks(@NotNull List<String> excludeScheduledTasks) {
this.excludeScheduledTasks = excludeScheduledTasks;
}

@Open
public static class Clients {
/** Enable automatic instrumentation of Spring-managed OkHttp clients. Disabled by default. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.sentry.NoOpTransportFactory
import io.sentry.ProfileLifecycle
import io.sentry.SamplingContext
import io.sentry.Sentry
import io.sentry.SentryEnvelope
import io.sentry.SentryEvent
import io.sentry.SentryIntegrationPackageStorage
import io.sentry.SentryLevel
Expand Down Expand Up @@ -50,6 +51,7 @@ import io.sentry.transport.ITransportGate
import io.sentry.transport.apache.ApacheHttpClientTransportFactory
import java.io.File
import java.lang.RuntimeException
import java.util.concurrent.TimeUnit
import javax.servlet.Filter
import kotlin.test.Test
import kotlin.test.assertEquals
Expand All @@ -58,9 +60,12 @@ import kotlin.test.assertTrue
import org.aspectj.lang.ProceedingJoinPoint
import org.assertj.core.api.Assertions.assertThat
import org.mockito.internal.util.MockUtil.isMock
import org.mockito.kotlin.after
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.timeout
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.quartz.JobExecutionContext
Expand All @@ -71,6 +76,7 @@ import org.quartz.core.QuartzScheduler
import org.slf4j.MDC
import org.springframework.aop.support.NameMatchMethodPointcut
import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
Expand All @@ -85,6 +91,8 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.scheduling.quartz.SchedulerFactoryBean
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.client.RestTemplate
Expand Down Expand Up @@ -1126,6 +1134,44 @@ class SentryAutoConfigurationTest {
.run { assertThat(it).doesNotHaveBean(SchedulerFactoryBeanCustomizer::class.java) }
}

@Test
fun `does not monitor @Scheduled methods by default`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj").run {
assertThat(it).doesNotHaveBean("sentryScheduledCheckInAdvisor")
}
}

@Test
fun `monitors @Scheduled methods when monitor-scheduled-tasks is enabled`() {
baseContextRunner
.withPropertyValues(
"sentry.dsn=http://key@localhost/proj",
"sentry.monitor-scheduled-tasks=true",
"sentry.exclude-scheduled-tasks[0]=SentryAutoConfigurationTest.ScheduledJobs.excluded",
)
.withConfiguration(AutoConfigurations.of(AopAutoConfiguration::class.java))
.withUserConfiguration(
MockTransportConfiguration::class.java,
ScheduledJobsConfiguration::class.java,
)
.run {
assertThat(it).hasBean("sentryScheduledCheckInAdvisor")
val transport = it.getBean(ITransport::class.java)
val captor = argumentCaptor<SentryEnvelope>()
verify(transport, timeout(5000).times(2)).send(any(), anyOrNull())
verify(transport, after(500).times(2)).send(captor.capture(), anyOrNull())
val payloads =
captor.allValues.map { envelope -> String(envelope.items.first().data, Charsets.UTF_8) }
assertThat(payloads).allMatch { payload ->
payload.contains(
"\"monitor_slug\":\"sentry-auto-configuration-test-scheduled-jobs-sync\""
)
}
assertThat(payloads[0]).contains("\"value\":1,\"unit\":\"hour\"")
assertThat(payloads[1]).contains("\"status\":\"ok\"")
}
}

@Test
fun `Sentry quartz job listener is added`() {
contextRunner
Expand Down Expand Up @@ -1241,6 +1287,18 @@ class SentryAutoConfigurationTest {
@Bean open fun sentryTransport() = transport
}

@Configuration(proxyBeanMethods = false)
@EnableScheduling
open class ScheduledJobsConfiguration {
@Bean open fun scheduledJobs() = ScheduledJobs()
}

open class ScheduledJobs {
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.HOURS) open fun sync() {}

@Scheduled(fixedRate = 1, timeUnit = TimeUnit.HOURS) open fun excluded() {}
}

@Configuration(proxyBeanMethods = false)
open class NoOpTransportConfiguration {

Expand Down
6 changes: 6 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public abstract interface annotation class io/sentry/spring/checkin/SentryCheckI
public class io/sentry/spring/checkin/SentryCheckInAdvice : org/aopalliance/intercept/MethodInterceptor, org/springframework/context/EmbeddedValueResolverAware {
public fun <init> ()V
public fun <init> (Lio/sentry/IScopes;)V
public fun <init> (Lio/sentry/IScopes;Z)V
public fun invoke (Lorg/aopalliance/intercept/MethodInvocation;)Ljava/lang/Object;
public fun setEmbeddedValueResolver (Lorg/springframework/util/StringValueResolver;)V
}
Expand All @@ -166,6 +167,11 @@ public class io/sentry/spring/checkin/SentryQuartzConfiguration {
public fun schedulerFactoryBeanCustomizer ()Lorg/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer;
}

public final class io/sentry/spring/checkin/SentryScheduledCheckInPointcut : org/springframework/aop/support/StaticMethodMatcherPointcut {
public fun <init> (Ljava/util/List;)V
public fun matches (Ljava/lang/reflect/Method;Ljava/lang/Class;)Z
}

public final class io/sentry/spring/checkin/SentrySchedulerFactoryBeanCustomizer : org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer {
public fun <init> ()V
public fun customize (Lorg/springframework/scheduling/quartz/SchedulerFactoryBean;)V
Expand Down
Loading
Loading