Skip to content

Commit

Permalink
SentryCheckIn annotation and advice config for non jakarta
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer committed Sep 21, 2023
1 parent 49adb9e commit e522b75
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 4 deletions.
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.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 @@ -182,6 +184,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 @@ -7,7 +7,6 @@
import io.sentry.IHub;
import io.sentry.SentryLevel;
import io.sentry.protocol.SentryId;
import io.sentry.spring.jakarta.tracing.SentryTransaction;
import io.sentry.util.Objects;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
Expand All @@ -20,8 +19,8 @@
import org.springframework.util.ObjectUtils;

/**
* Reports execution of every bean method annotated with {@link SentryTransaction} or a execution of
* a bean method within a class annotated with {@link SentryTransaction}.
* Reports execution of every bean method annotated with {@link SentryCheckIn} as a monitor
* check-in.
*/
@ApiStatus.Internal
@ApiStatus.Experimental
Expand Down
22 changes: 22 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ public final class io/sentry/spring/SpringSecuritySentryUserProvider : io/sentry
public fun provideUser ()Lio/sentry/protocol/User;
}

public abstract interface annotation class io/sentry/spring/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/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/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/checkin/SentryCheckInPointcutConfiguration {
public fun <init> ()V
public fun sentryCheckInPointcut ()Lorg/springframework/aop/Pointcut;
}

public class io/sentry/spring/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.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, ElementType.TYPE})
@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.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");
}

Check warning on line 33 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L31-L33

Added lines #L31 - L33 were not covered by tests

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

Check warning on line 38 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L37-L38

Added lines #L37 - L38 were not covered by tests

@Nullable
SentryCheckIn checkInAnnotation =
AnnotationUtils.findAnnotation(mostSpecificMethod, SentryCheckIn.class);

Check warning on line 42 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L41-L42

Added lines #L41 - L42 were not covered by tests
if (checkInAnnotation == null) {
return invocation.proceed();

Check warning on line 44 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L44

Added line #L44 was not covered by tests
}

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

Check warning on line 48 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L47-L48

Added lines #L47 - L48 were not covered by tests

if (ObjectUtils.isEmpty(monitorSlug)) {
hub.getOptions()
.getLogger()
.log(

Check warning on line 53 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L51-L53

Added lines #L51 - L53 were not covered by tests
SentryLevel.WARNING,
"Not capturing check-in for method annotated with @SentryCheckIn because it does not specify a monitor slug.");
return invocation.proceed();

Check warning on line 56 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L56

Added line #L56 was not covered by tests
}

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

Check warning on line 61 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L59-L61

Added lines #L59 - L61 were not covered by tests

try {
if (!isHeartbeatOnly) {
checkInId = hub.captureCheckIn(new CheckIn(monitorSlug, CheckInStatus.IN_PROGRESS));

Check warning on line 65 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L65

Added line #L65 was not covered by tests
}
return invocation.proceed();
} catch (Throwable e) {
didError = true;
throw e;

Check warning on line 70 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L67-L70

Added lines #L67 - L70 were not covered by tests
} 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);

Check warning on line 75 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java#L73-L75

Added lines #L73 - L75 were not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.sentry.spring.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 {

Check warning on line 18 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java#L18

Added line #L18 was not covered by tests

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

Check warning on line 22 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java#L22

Added line #L22 was not covered by tests
}

@Bean
public @NotNull Advisor sentryCheckInAdvisor(
final @NotNull @Qualifier("sentryCheckInPointcut") Pointcut sentryCheckInPointcut,
final @NotNull @Qualifier("sentryCheckInAdvice") Advice sentryCheckInAdvice) {
return new DefaultPointcutAdvisor(sentryCheckInPointcut, sentryCheckInAdvice);

Check warning on line 29 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdviceConfiguration.java#L29

Added line #L29 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.spring.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 {

Check warning on line 17 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInPointcutConfiguration.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInPointcutConfiguration.java#L17

Added line #L17 was not covered by tests

/**
* 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));

Check warning on line 27 in sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInPointcutConfiguration.java

View check run for this annotation

Codecov / codecov/patch

sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInPointcutConfiguration.java#L26-L27

Added lines #L26 - L27 were not covered by tests
}
}

0 comments on commit e522b75

Please sign in to comment.