Skip to content

Commit

Permalink
Merge e37edb4 into c65470b
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer authored Sep 27, 2023
2 parents c65470b + e37edb4 commit 24b1f58
Show file tree
Hide file tree
Showing 18 changed files with 796 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add API for sending checkins (CRONS) manually ([#2935](https://github.com/getsentry/sentry-java/pull/2935))
- Support check-ins (CRONS) for Quartz ([#2940](https://github.com/getsentry/sentry-java/pull/2940))
- Add option for ignoring certain monitor slugs ([#2943](https://github.com/getsentry/sentry-java/pull/2943))
- `@SentryCheckIn` annotation and advice config for Spring ([#2946](https://github.com/getsentry/sentry-java/pull/2946))

### Fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package io.sentry.samples.spring.boot.jakarta;

import io.sentry.CheckIn;
import io.sentry.CheckInStatus;
import io.sentry.DateUtils;
import io.sentry.Sentry;
import io.sentry.protocol.SentryId;
import io.sentry.spring.jakarta.checkin.SentryCheckIn;
import io.sentry.spring.jakarta.tracing.SentryTransaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -22,23 +17,10 @@ public class CustomJob {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomJob.class);

@SentryCheckIn("monitor_slug_1")
@Scheduled(fixedRate = 3 * 60 * 1000L)
void execute() throws InterruptedException {
final @NotNull SentryId checkInId =
Sentry.captureCheckIn(new CheckIn("my_monitor_slug", CheckInStatus.IN_PROGRESS));
final long startTime = System.currentTimeMillis();
boolean didError = false;
try {
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
} catch (Throwable t) {
didError = true;
throw t;
} finally {
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
CheckIn checkIn = new CheckIn(checkInId, "my_monitor_slug", status);
checkIn.setDuration(DateUtils.millisToSeconds(System.currentTimeMillis() - startTime));
Sentry.captureCheckIn(checkIn);
}
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.samples.spring.boot;

import io.sentry.spring.checkin.SentryCheckIn;
import io.sentry.spring.tracing.SentryTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -16,7 +17,8 @@ public class CustomJob {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomJob.class);

@Scheduled(fixedRate = 3 * 1000L)
@Scheduled(fixedRate = 3 * 60 * 1000L)
@SentryCheckIn("monitor_slug_2")
void execute() throws InterruptedException {
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import io.sentry.spring.jakarta.SentryUserProvider;
import io.sentry.spring.jakarta.SentryWebConfiguration;
import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider;
import io.sentry.spring.jakarta.checkin.SentryCheckInAdviceConfiguration;
import io.sentry.spring.jakarta.checkin.SentryCheckInPointcutConfiguration;
import io.sentry.spring.jakarta.checkin.SentryQuartzConfiguration;
import io.sentry.spring.jakarta.graphql.SentryGraphqlConfiguration;
import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration;
Expand Down Expand Up @@ -181,6 +183,22 @@ static class GraphqlConfiguration {}
})
static class QuartzConfiguration {}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ProceedingJoinPoint.class)
@ConditionalOnProperty(
value = "sentry.enable-aot-compatibility",
havingValue = "false",
matchIfMissing = true)
@Import(SentryCheckInAdviceConfiguration.class)
@Open
static class SentryCheckInAspectsConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "sentryCheckInPointcut")
@Import(SentryCheckInPointcutConfiguration.class)
@Open
static class SentryCheckInPointcutAutoConfiguration {}
}

/** Registers beans specific to Spring MVC. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import io.sentry.spring.SentryUserProvider;
import io.sentry.spring.SentryWebConfiguration;
import io.sentry.spring.SpringSecuritySentryUserProvider;
import io.sentry.spring.checkin.SentryCheckInAdviceConfiguration;
import io.sentry.spring.checkin.SentryCheckInPointcutConfiguration;
import io.sentry.spring.checkin.SentryQuartzConfiguration;
import io.sentry.spring.graphql.SentryGraphqlConfiguration;
import io.sentry.spring.tracing.SentryAdviceConfiguration;
Expand Down Expand Up @@ -181,6 +183,22 @@ static class GraphqlConfiguration {}
})
static class QuartzConfiguration {}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ProceedingJoinPoint.class)
@ConditionalOnProperty(
value = "sentry.enable-aot-compatibility",
havingValue = "false",
matchIfMissing = true)
@Import(SentryCheckInAdviceConfiguration.class)
@Open
static class SentryCheckInAspectsConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "sentryCheckInPointcut")
@Import(SentryCheckInPointcutConfiguration.class)
@Open
static class SentryCheckInPointcutAutoConfiguration {}
}

/** Registers beans specific to Spring MVC. */
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
Expand Down
22 changes: 22 additions & 0 deletions sentry-spring-jakarta/api/sentry-spring-jakarta.api
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ public final class io/sentry/spring/jakarta/SpringSecuritySentryUserProvider : i
public fun provideUser ()Lio/sentry/protocol/User;
}

public abstract interface annotation class io/sentry/spring/jakarta/checkin/SentryCheckIn : java/lang/annotation/Annotation {
public abstract fun heartbeat ()Z
public abstract fun monitorSlug ()Ljava/lang/String;
public abstract fun value ()Ljava/lang/String;
}

public class io/sentry/spring/jakarta/checkin/SentryCheckInAdvice : org/aopalliance/intercept/MethodInterceptor {
public fun <init> (Lio/sentry/IHub;)V
public fun invoke (Lorg/aopalliance/intercept/MethodInvocation;)Ljava/lang/Object;
}

public class io/sentry/spring/jakarta/checkin/SentryCheckInAdviceConfiguration {
public fun <init> ()V
public fun sentryCheckInAdvice (Lio/sentry/IHub;)Lorg/aopalliance/aop/Advice;
public fun sentryCheckInAdvisor (Lorg/springframework/aop/Pointcut;Lorg/aopalliance/aop/Advice;)Lorg/springframework/aop/Advisor;
}

public class io/sentry/spring/jakarta/checkin/SentryCheckInPointcutConfiguration {
public fun <init> ()V
public fun sentryCheckInPointcut ()Lorg/springframework/aop/Pointcut;
}

public class io/sentry/spring/jakarta/checkin/SentryQuartzConfiguration {
public fun <init> ()V
public fun schedulerFactoryBeanCustomizer ()Lorg/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.sentry.spring.jakarta.checkin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jetbrains.annotations.ApiStatus;
import org.springframework.core.annotation.AliasFor;

/** Sends a {@link io.sentry.CheckIn} for the annotated method. */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@ApiStatus.Experimental
public @interface SentryCheckIn {

/**
* Monitor slug. If not set, no check-in will be sent.
*
* @return monitor slug
*/
@AliasFor("value")
String monitorSlug() default "";

/**
* Whether to send only send heartbeat events.
*
* <p>A hearbeat check-in means there's no separate IN_PROGRESS check-in at the start of the jobs
* execution. Only the check-in after finishing the job will be sent.
*
* @return true if only heartbeat check-ins should be sent.
*/
boolean heartbeat() default false;

/**
* Monitor slug. If not set, no check-in will be sent.
*
* @return monitor slug
*/
@AliasFor("monitorSlug")
String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.sentry.spring.jakarta.checkin;

import com.jakewharton.nopen.annotation.Open;
import io.sentry.CheckIn;
import io.sentry.CheckInStatus;
import io.sentry.DateUtils;
import io.sentry.IHub;
import io.sentry.SentryLevel;
import io.sentry.protocol.SentryId;
import io.sentry.util.Objects;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;

/**
* Reports execution of every bean method annotated with {@link SentryCheckIn} as a monitor
* check-in.
*/
@ApiStatus.Internal
@ApiStatus.Experimental
@Open
public class SentryCheckInAdvice implements MethodInterceptor {
private final @NotNull IHub hub;

public SentryCheckInAdvice(final @NotNull IHub hub) {
this.hub = Objects.requireNonNull(hub, "hub is required");
}

@Override
public Object invoke(final @NotNull MethodInvocation invocation) throws Throwable {
final Method mostSpecificMethod =
AopUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass());

@Nullable
SentryCheckIn checkInAnnotation =
AnnotationUtils.findAnnotation(mostSpecificMethod, SentryCheckIn.class);
if (checkInAnnotation == null) {
return invocation.proceed();
}

final boolean isHeartbeatOnly = checkInAnnotation.heartbeat();
final @Nullable String monitorSlug = checkInAnnotation.value();

if (ObjectUtils.isEmpty(monitorSlug)) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.WARNING,
"Not capturing check-in for method annotated with @SentryCheckIn because it does not specify a monitor slug.");
return invocation.proceed();
}

@Nullable SentryId checkInId = null;
final long startTime = System.currentTimeMillis();
boolean didError = false;

try {
if (!isHeartbeatOnly) {
checkInId = hub.captureCheckIn(new CheckIn(monitorSlug, CheckInStatus.IN_PROGRESS));
}
return invocation.proceed();
} catch (Throwable e) {
didError = true;
throw e;
} finally {
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status);
checkIn.setDuration(DateUtils.millisToSeconds(System.currentTimeMillis() - startTime));
hub.captureCheckIn(checkIn);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.sentry.spring.jakarta.checkin;

import com.jakewharton.nopen.annotation.Open;
import io.sentry.IHub;
import org.aopalliance.aop.Advice;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@Open
@ApiStatus.Experimental
public class SentryCheckInAdviceConfiguration {

@Bean
public @NotNull Advice sentryCheckInAdvice(final @NotNull IHub hub) {
return new SentryCheckInAdvice(hub);
}

@Bean
public @NotNull Advisor sentryCheckInAdvisor(
final @NotNull @Qualifier("sentryCheckInPointcut") Pointcut sentryCheckInPointcut,
final @NotNull @Qualifier("sentryCheckInAdvice") Advice sentryCheckInAdvice) {
return new DefaultPointcutAdvisor(sentryCheckInPointcut, sentryCheckInAdvice);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.spring.jakarta.checkin;

import com.jakewharton.nopen.annotation.Open;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationClassFilter;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** AOP pointcut configuration for {@link SentryCheckIn}. */
@Configuration(proxyBeanMethods = false)
@Open
@ApiStatus.Experimental
public class SentryCheckInPointcutConfiguration {

/**
* Pointcut around which check-ins are created.
*
* @return pointcut used by {@link SentryCheckInAdvice}.
*/
@Bean
public @NotNull Pointcut sentryCheckInPointcut() {
return new ComposablePointcut(new AnnotationClassFilter(SentryCheckIn.class, true))
.union(new AnnotationMatchingPointcut(null, SentryCheckIn.class));
}
}
Loading

0 comments on commit 24b1f58

Please sign in to comment.