Skip to content

Commit c1eff76

Browse files
adinauerlbloder
andauthored
CRONS support for Quartz and Spring @Scheduled (#2952)
Co-authored-by: Lukas Bloder <[email protected]>
1 parent 8d29d97 commit c1eff76

File tree

67 files changed

+1629
-33
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1629
-33
lines changed

.craft.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ targets:
4848
maven:io.sentry:sentry-apollo:
4949
maven:io.sentry:sentry-jdbc:
5050
maven:io.sentry:sentry-graphql:
51+
# maven:io.sentry:sentry-quartz:
5152
maven:io.sentry:sentry-android-navigation:
5253
maven:io.sentry:sentry-compose:
5354
maven:io.sentry:sentry-compose-android:

.github/ISSUE_TEMPLATE/bug_report_java.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ body:
2727
- sentry-logback
2828
- sentry-log4j2
2929
- sentry-graphql
30+
- sentry-quartz
3031
- sentry-openfeign
3132
- sentry-apache-http-client-5
3233
- other

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
- Add `sendModules` option for disable sending modules ([#2926](https://github.com/getsentry/sentry-java/pull/2926))
88
- Send `db.system` and `db.name` in span data for androidx.sqlite spans ([#2928](https://github.com/getsentry/sentry-java/pull/2928))
9-
- Add API for sending checkins (CRONS) manually ([#2935](https://github.com/getsentry/sentry-java/pull/2935))
9+
- Check-ins (CRONS) support ([#2952](https://github.com/getsentry/sentry-java/pull/2952))
10+
- Add API for sending check-ins (CRONS) manually ([#2935](https://github.com/getsentry/sentry-java/pull/2935))
11+
- Support check-ins (CRONS) for Quartz ([#2940](https://github.com/getsentry/sentry-java/pull/2940))
12+
- `@SentryCheckIn` annotation and advice config for Spring ([#2946](https://github.com/getsentry/sentry-java/pull/2946))
13+
- Add option for ignoring certain monitor slugs ([#2943](https://github.com/getsentry/sentry-java/pull/2943))
1014

1115
### Fixes
1216

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Sentry SDK for Java and Android
4848
| sentry-log4j2 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-log4j2/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-log4j2) |
4949
| sentry-bom | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-bom/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-bom) |
5050
| sentry-graphql | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-graphql/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-graphql) |
51+
| sentry-quartz | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-quartz/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-quartz) |
5152
| sentry-openfeign | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-openfeign/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-openfeign) |
5253
| sentry-opentelemetry-agent | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-opentelemetry-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-opentelemetry-agent) |
5354
| sentry-opentelemetry-agentcustomization | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-opentelemetry-agentcustomization/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.sentry/sentry-opentelemetry-agentcustomization) |

buildSrc/src/main/java/Config.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ object Config {
7575

7676
val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
7777
val springBootStarterGraphql = "org.springframework.boot:spring-boot-starter-graphql:$springBootVersion"
78+
val springBootStarterQuartz = "org.springframework.boot:spring-boot-starter-quartz:$springBootVersion"
7879
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
7980
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
8081
val springBootStarterWebsocket = "org.springframework.boot:spring-boot-starter-websocket:$springBootVersion"
@@ -85,6 +86,7 @@ object Config {
8586

8687
val springBoot3Starter = "org.springframework.boot:spring-boot-starter:$springBoot3Version"
8788
val springBoot3StarterGraphql = "org.springframework.boot:spring-boot-starter-graphql:$springBoot3Version"
89+
val springBoot3StarterQuartz = "org.springframework.boot:spring-boot-starter-quartz:$springBoot3Version"
8890
val springBoot3StarterTest = "org.springframework.boot:spring-boot-starter-test:$springBoot3Version"
8991
val springBoot3StarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBoot3Version"
9092
val springBoot3StarterWebsocket = "org.springframework.boot:spring-boot-starter-websocket:$springBoot3Version"
@@ -128,6 +130,8 @@ object Config {
128130

129131
val graphQlJava = "com.graphql-java:graphql-java:17.3"
130132

133+
val quartz = "org.quartz-scheduler:quartz:2.3.0"
134+
131135
val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect"
132136
val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib"
133137

@@ -227,6 +231,7 @@ object Config {
227231
val SENTRY_APOLLO3_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo3"
228232
val SENTRY_APOLLO_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo"
229233
val SENTRY_GRAPHQL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql"
234+
val SENTRY_QUARTZ_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.quartz"
230235
val SENTRY_JDBC_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jdbc"
231236
val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet"
232237
val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
public final class io/sentry/quartz/BuildConfig {
2+
public static final field SENTRY_QUARTZ_SDK_NAME Ljava/lang/String;
3+
public static final field VERSION_NAME Ljava/lang/String;
4+
}
5+
6+
public final class io/sentry/quartz/SentryJobListener : org/quartz/JobListener {
7+
public static final field SENTRY_CHECK_IN_ID_KEY Ljava/lang/String;
8+
public static final field SENTRY_SLUG_KEY Ljava/lang/String;
9+
public fun <init> ()V
10+
public fun <init> (Lio/sentry/IHub;)V
11+
public fun getName ()Ljava/lang/String;
12+
public fun jobExecutionVetoed (Lorg/quartz/JobExecutionContext;)V
13+
public fun jobToBeExecuted (Lorg/quartz/JobExecutionContext;)V
14+
public fun jobWasExecuted (Lorg/quartz/JobExecutionContext;Lorg/quartz/JobExecutionException;)V
15+
}
16+

sentry-quartz/build.gradle.kts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import net.ltgt.gradle.errorprone.errorprone
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
`java-library`
6+
kotlin("jvm")
7+
jacoco
8+
id(Config.QualityPlugins.errorProne)
9+
id(Config.QualityPlugins.gradleVersions)
10+
id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion
11+
}
12+
13+
configure<JavaPluginExtension> {
14+
sourceCompatibility = JavaVersion.VERSION_1_8
15+
targetCompatibility = JavaVersion.VERSION_1_8
16+
}
17+
18+
tasks.withType<KotlinCompile>().configureEach {
19+
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
20+
kotlinOptions.languageVersion = Config.kotlinCompatibleLanguageVersion
21+
}
22+
23+
dependencies {
24+
api(projects.sentry)
25+
compileOnly(Config.Libs.quartz)
26+
27+
compileOnly(Config.CompileOnly.nopen)
28+
errorprone(Config.CompileOnly.nopenChecker)
29+
errorprone(Config.CompileOnly.errorprone)
30+
errorprone(Config.CompileOnly.errorProneNullAway)
31+
compileOnly(Config.CompileOnly.jetbrainsAnnotations)
32+
33+
// tests
34+
testImplementation(projects.sentry)
35+
testImplementation(projects.sentryTestSupport)
36+
testImplementation(kotlin(Config.kotlinStdLib))
37+
testImplementation(Config.TestLibs.kotlinTestJunit)
38+
testImplementation(Config.TestLibs.mockitoKotlin)
39+
testImplementation(Config.TestLibs.mockitoInline)
40+
}
41+
42+
configure<SourceSetContainer> {
43+
test {
44+
java.srcDir("src/test/java")
45+
}
46+
}
47+
48+
jacoco {
49+
toolVersion = Config.QualityPlugins.Jacoco.version
50+
}
51+
52+
tasks.jacocoTestReport {
53+
reports {
54+
xml.required.set(true)
55+
html.required.set(false)
56+
}
57+
}
58+
59+
tasks {
60+
jacocoTestCoverageVerification {
61+
violationRules {
62+
rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
63+
}
64+
}
65+
check {
66+
dependsOn(jacocoTestCoverageVerification)
67+
dependsOn(jacocoTestReport)
68+
}
69+
}
70+
71+
tasks.withType<JavaCompile>().configureEach {
72+
options.errorprone {
73+
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
74+
option("NullAway:AnnotatedPackages", "io.sentry")
75+
}
76+
}
77+
78+
buildConfig {
79+
useJavaOutput()
80+
packageName("io.sentry.quartz")
81+
buildConfigField("String", "SENTRY_QUARTZ_SDK_NAME", "\"${Config.Sentry.SENTRY_QUARTZ_SDK_NAME}\"")
82+
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
83+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.sentry.quartz;
2+
3+
import io.sentry.BuildConfig;
4+
import io.sentry.CheckIn;
5+
import io.sentry.CheckInStatus;
6+
import io.sentry.HubAdapter;
7+
import io.sentry.IHub;
8+
import io.sentry.SentryIntegrationPackageStorage;
9+
import io.sentry.SentryLevel;
10+
import io.sentry.protocol.SentryId;
11+
import io.sentry.util.Objects;
12+
import io.sentry.util.TracingUtils;
13+
import org.jetbrains.annotations.ApiStatus;
14+
import org.jetbrains.annotations.NotNull;
15+
import org.jetbrains.annotations.Nullable;
16+
import org.quartz.JobDataMap;
17+
import org.quartz.JobExecutionContext;
18+
import org.quartz.JobExecutionException;
19+
import org.quartz.JobListener;
20+
21+
@ApiStatus.Experimental
22+
public final class SentryJobListener implements JobListener {
23+
24+
public static final String SENTRY_CHECK_IN_ID_KEY = "sentry-checkin-id";
25+
public static final String SENTRY_SLUG_KEY = "sentry-slug";
26+
27+
private final @NotNull IHub hub;
28+
29+
public SentryJobListener() {
30+
this(HubAdapter.getInstance());
31+
}
32+
33+
public SentryJobListener(final @NotNull IHub hub) {
34+
this.hub = Objects.requireNonNull(hub, "hub is required");
35+
SentryIntegrationPackageStorage.getInstance().addIntegration("Quartz");
36+
SentryIntegrationPackageStorage.getInstance()
37+
.addPackage("maven:io.sentry:sentry-quartz", BuildConfig.VERSION_NAME);
38+
}
39+
40+
@Override
41+
public String getName() {
42+
return "sentry-job-listener";
43+
}
44+
45+
@Override
46+
public void jobToBeExecuted(final @NotNull JobExecutionContext context) {
47+
try {
48+
final @Nullable String maybeSlug = getSlug(context);
49+
if (maybeSlug == null) {
50+
return;
51+
}
52+
hub.pushScope();
53+
TracingUtils.startNewTrace(hub);
54+
final @NotNull String slug = maybeSlug;
55+
final @NotNull CheckIn checkIn = new CheckIn(slug, CheckInStatus.IN_PROGRESS);
56+
final @NotNull SentryId checkInId = hub.captureCheckIn(checkIn);
57+
context.put(SENTRY_CHECK_IN_ID_KEY, checkInId);
58+
context.put(SENTRY_SLUG_KEY, slug);
59+
} catch (Throwable t) {
60+
hub.getOptions()
61+
.getLogger()
62+
.log(SentryLevel.ERROR, "Unable to capture check-in in jobToBeExecuted.", t);
63+
}
64+
}
65+
66+
private @Nullable String getSlug(final @NotNull JobExecutionContext context) {
67+
final @Nullable JobDataMap jobDataMap = context.getMergedJobDataMap();
68+
if (jobDataMap != null) {
69+
final @Nullable Object o = jobDataMap.get(SENTRY_SLUG_KEY);
70+
if (o != null) {
71+
return o.toString();
72+
}
73+
}
74+
75+
return null;
76+
}
77+
78+
@Override
79+
public void jobExecutionVetoed(JobExecutionContext context) {
80+
// do nothing
81+
}
82+
83+
@Override
84+
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
85+
try {
86+
final @Nullable Object checkInIdObjectFromContext = context.get(SENTRY_CHECK_IN_ID_KEY);
87+
final @Nullable Object slugObjectFromContext = context.get(SENTRY_SLUG_KEY);
88+
final @NotNull SentryId checkInId =
89+
checkInIdObjectFromContext == null
90+
? new SentryId()
91+
: (SentryId) checkInIdObjectFromContext;
92+
final @Nullable String slug =
93+
slugObjectFromContext == null ? null : (String) slugObjectFromContext;
94+
if (slug != null) {
95+
final boolean isFailed = jobException != null;
96+
final @NotNull CheckInStatus status = isFailed ? CheckInStatus.ERROR : CheckInStatus.OK;
97+
hub.captureCheckIn(new CheckIn(checkInId, slug, status));
98+
}
99+
} catch (Throwable t) {
100+
hub.getOptions()
101+
.getLogger()
102+
.log(SentryLevel.ERROR, "Unable to capture check-in in jobWasExecuted.", t);
103+
} finally {
104+
hub.popScope();
105+
}
106+
}
107+
}

sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
implementation(Config.Libs.springBoot3StarterWeb)
2323
implementation(Config.Libs.springBoot3StarterWebsocket)
2424
implementation(Config.Libs.springBoot3StarterGraphql)
25+
implementation(Config.Libs.springBoot3StarterQuartz)
2526
implementation(Config.Libs.springBoot3StarterWebflux)
2627
implementation(Config.Libs.springBoot3StarterAop)
2728
implementation(Config.Libs.aspectj)
@@ -32,6 +33,7 @@ dependencies {
3233
implementation(projects.sentrySpringBootStarterJakarta)
3334
implementation(projects.sentryLogback)
3435
implementation(projects.sentryGraphql)
36+
implementation(projects.sentryQuartz)
3537

3638
// database query tracing
3739
implementation(projects.sentryJdbc)
Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
package io.sentry.samples.spring.boot.jakarta;
22

3-
import io.sentry.CheckIn;
4-
import io.sentry.CheckInStatus;
5-
import io.sentry.DateUtils;
6-
import io.sentry.Sentry;
7-
import io.sentry.protocol.SentryId;
3+
import io.sentry.spring.jakarta.checkin.SentryCheckIn;
84
import io.sentry.spring.jakarta.tracing.SentryTransaction;
9-
import org.jetbrains.annotations.NotNull;
105
import org.slf4j.Logger;
116
import org.slf4j.LoggerFactory;
127
import org.springframework.scheduling.annotation.Scheduled;
@@ -22,24 +17,10 @@ public class CustomJob {
2217

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

20+
@SentryCheckIn("monitor_slug_1")
2521
@Scheduled(fixedRate = 3 * 60 * 1000L)
2622
void execute() throws InterruptedException {
27-
final @NotNull SentryId checkInId =
28-
Sentry.captureCheckIn(new CheckIn("my_monitor_slug", CheckInStatus.IN_PROGRESS));
29-
final long startTime = System.currentTimeMillis();
30-
boolean didError = false;
31-
try {
32-
LOGGER.info("Executing scheduled job");
33-
Thread.sleep(2000L);
34-
Sentry.captureCheckIn(new CheckIn(checkInId, "my_monitor_slug", CheckInStatus.OK));
35-
} catch (Throwable t) {
36-
didError = true;
37-
throw t;
38-
} finally {
39-
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
40-
CheckIn checkIn = new CheckIn(checkInId, "my_monitor_slug", status);
41-
checkIn.setDuration(DateUtils.millisToSeconds(System.currentTimeMillis() - startTime));
42-
Sentry.captureCheckIn(checkIn);
43-
}
23+
LOGGER.info("Executing scheduled job");
24+
Thread.sleep(2000L);
4425
}
4526
}

0 commit comments

Comments
 (0)