Skip to content

Commit

Permalink
Merge f4fb1dd into 8d29d97
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer committed Sep 20, 2023
2 parents 8d29d97 + f4fb1dd commit 20bea77
Show file tree
Hide file tree
Showing 30 changed files with 537 additions and 4 deletions.
1 change: 1 addition & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ targets:
maven:io.sentry:sentry-apollo:
maven:io.sentry:sentry-jdbc:
maven:io.sentry:sentry-graphql:
# maven:io.sentry:sentry-quartz:
maven:io.sentry:sentry-android-navigation:
maven:io.sentry:sentry-compose:
maven:io.sentry:sentry-compose-android:
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report_java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ body:
- sentry-logback
- sentry-log4j2
- sentry-graphql
- sentry-quartz
- sentry-openfeign
- sentry-apache-http-client-5
- other
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Sentry SDK for Java and Android
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
Expand Down
5 changes: 5 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ object Config {

val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
val springBootStarterGraphql = "org.springframework.boot:spring-boot-starter-graphql:$springBootVersion"
val springBootStarterQuartz = "org.springframework.boot:spring-boot-starter-quartz:$springBootVersion"
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
val springBootStarterWebsocket = "org.springframework.boot:spring-boot-starter-websocket:$springBootVersion"
Expand All @@ -85,6 +86,7 @@ object Config {

val springBoot3Starter = "org.springframework.boot:spring-boot-starter:$springBoot3Version"
val springBoot3StarterGraphql = "org.springframework.boot:spring-boot-starter-graphql:$springBoot3Version"
val springBoot3StarterQuartz = "org.springframework.boot:spring-boot-starter-quartz:$springBoot3Version"
val springBoot3StarterTest = "org.springframework.boot:spring-boot-starter-test:$springBoot3Version"
val springBoot3StarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBoot3Version"
val springBoot3StarterWebsocket = "org.springframework.boot:spring-boot-starter-websocket:$springBoot3Version"
Expand Down Expand Up @@ -128,6 +130,8 @@ object Config {

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

val quartz = "org.quartz-scheduler:quartz:2.3.0"

val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect"
val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib"

Expand Down Expand Up @@ -227,6 +231,7 @@ object Config {
val SENTRY_APOLLO3_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo3"
val SENTRY_APOLLO_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo"
val SENTRY_GRAPHQL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql"
val SENTRY_QUARTZ_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.quartz"
val SENTRY_JDBC_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.jdbc"
val SENTRY_SERVLET_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet"
val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta"
Expand Down
15 changes: 15 additions & 0 deletions sentry-quartz/api/sentry-quartz.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public final class io/sentry/quartz/BuildConfig {
public static final field SENTRY_QUARTZ_SDK_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
}

public final class io/sentry/quartz/SentryJobListener : org/quartz/JobListener {
public static final field SENTRY_CHECK_IN_ID_KEY Ljava/lang/String;
public static final field SENTRY_CHECK_IN_SLUG_KEY Ljava/lang/String;
public fun <init> ()V
public fun getName ()Ljava/lang/String;
public fun jobExecutionVetoed (Lorg/quartz/JobExecutionContext;)V
public fun jobToBeExecuted (Lorg/quartz/JobExecutionContext;)V
public fun jobWasExecuted (Lorg/quartz/JobExecutionContext;Lorg/quartz/JobExecutionException;)V
}

83 changes: 83 additions & 0 deletions sentry-quartz/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`java-library`
kotlin("jvm")
jacoco
id(Config.QualityPlugins.errorProne)
id(Config.QualityPlugins.gradleVersions)
id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion
}

configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
kotlinOptions.languageVersion = Config.kotlinCompatibleLanguageVersion
}

dependencies {
api(projects.sentry)
compileOnly(Config.Libs.quartz)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
errorprone(Config.CompileOnly.errorprone)
errorprone(Config.CompileOnly.errorProneNullAway)
compileOnly(Config.CompileOnly.jetbrainsAnnotations)

// tests
testImplementation(projects.sentry)
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.mockitoInline)
}

configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
}
}

jacoco {
toolVersion = Config.QualityPlugins.Jacoco.version
}

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(false)
}
}

tasks {
jacocoTestCoverageVerification {
violationRules {
rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
}
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
}
}

tasks.withType<JavaCompile>().configureEach {
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.quartz")
buildConfigField("String", "SENTRY_QUARTZ_SDK_NAME", "\"${Config.Sentry.SENTRY_QUARTZ_SDK_NAME}\"")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}
206 changes: 206 additions & 0 deletions sentry-quartz/src/main/java/io/sentry/quartz/SentryJobListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package io.sentry.quartz;

import io.sentry.BuildConfig;
import io.sentry.CheckIn;
import io.sentry.CheckInStatus;
import io.sentry.MonitorConfig;
import io.sentry.MonitorSchedule;
import io.sentry.MonitorScheduleUnit;
import io.sentry.Sentry;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryLevel;
import io.sentry.protocol.SentryId;
import java.util.List;
import java.util.TimeZone;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.JobListener;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;

public final class SentryJobListener implements JobListener {

public static final String SENTRY_CHECK_IN_ID_KEY = "sentry-checkin-id";
public static final String SENTRY_CHECK_IN_SLUG_KEY = "sentry-checkin-slug";

public SentryJobListener() {
SentryIntegrationPackageStorage.getInstance().addIntegration("Quartz");
SentryIntegrationPackageStorage.getInstance()
.addPackage("maven:io.sentry:sentry-quartz", BuildConfig.VERSION_NAME);
}

@Override
public String getName() {
return "sentry-job-listener";
}

@Override
public void jobToBeExecuted(JobExecutionContext context) {
try {
final @NotNull String slug = getSlug(context.getJobDetail());
final @NotNull CheckIn checkIn = new CheckIn(slug, CheckInStatus.IN_PROGRESS);

final @Nullable MonitorConfig monitorConfig = extractMonitorConfig(context);
if (monitorConfig != null) {
checkIn.setMonitorConfig(monitorConfig);
}

final @NotNull SentryId checkInId = Sentry.captureCheckIn(checkIn);
context.put(SENTRY_CHECK_IN_ID_KEY, checkInId);
context.put(SENTRY_CHECK_IN_SLUG_KEY, slug);
} catch (Throwable t) {
Sentry.getCurrentHub()
.getOptions()
.getLogger()
.log(SentryLevel.ERROR, "Unable to capture check-in in jobToBeExecuted.", t);
}
}

private @NotNull String getSlug(final @Nullable JobDetail jobDetail) {
if (jobDetail == null) {
return "fallback";
}
final @NotNull StringBuilder slugBuilder = new StringBuilder();

final @Nullable JobKey key = jobDetail.getKey();
if (key != null) {
slugBuilder.append(key.getName());
slugBuilder.append("__");
}

final @Nullable Class<? extends Job> jobClass = jobDetail.getJobClass();
if (jobClass != null) {
slugBuilder.append(jobClass.getCanonicalName());
}

return slugBuilder.toString();
}

private @Nullable MonitorConfig extractMonitorConfig(final @NotNull JobExecutionContext context) {
@Nullable MonitorSchedule schedule = null;
@Nullable String cronExpression = null;
@Nullable TimeZone timeZone = TimeZone.getDefault();
@Nullable Integer repeatInterval = null;
@Nullable MonitorScheduleUnit timeUnit = null;

try {
List<? extends Trigger> triggersOfJob =
context.getScheduler().getTriggersOfJob(context.getTrigger().getJobKey());
for (Trigger trigger : triggersOfJob) {
if (trigger instanceof CronTrigger) {
final CronTrigger cronTrigger = (CronTrigger) trigger;
cronExpression = cronTrigger.getCronExpression();
timeZone = cronTrigger.getTimeZone();
} else if (trigger instanceof SimpleTrigger) {
final SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
long tmpRepeatInterval = simpleTrigger.getRepeatInterval();
repeatInterval = millisToMinutes(Double.valueOf(tmpRepeatInterval));
timeUnit = MonitorScheduleUnit.MINUTE;
} else if (trigger instanceof CalendarIntervalTrigger) {
final CalendarIntervalTrigger calendarIntervalTrigger = (CalendarIntervalTrigger) trigger;
DateBuilder.IntervalUnit repeatIntervalUnit =
calendarIntervalTrigger.getRepeatIntervalUnit();
int tmpRepeatInterval = calendarIntervalTrigger.getRepeatInterval();
if (DateBuilder.IntervalUnit.SECOND.equals(repeatIntervalUnit)) {
repeatInterval = secondsToMinutes(Double.valueOf(tmpRepeatInterval));
timeUnit = MonitorScheduleUnit.MINUTE;
} else if (DateBuilder.IntervalUnit.MILLISECOND.equals(repeatIntervalUnit)) {
repeatInterval = millisToMinutes(Double.valueOf(tmpRepeatInterval));
timeUnit = MonitorScheduleUnit.MINUTE;
} else {
repeatInterval = tmpRepeatInterval;
timeUnit = convertUnit(repeatIntervalUnit);
}
}
}
} catch (Throwable t) {
Sentry.getCurrentHub()
.getOptions()
.getLogger()
.log(SentryLevel.ERROR, "Unable to extract monitor config for check-in.", t);
}
if (cronExpression != null) {
schedule = MonitorSchedule.crontab(cronExpression);
} else if (repeatInterval != null && timeUnit != null) {
schedule = MonitorSchedule.interval(repeatInterval.intValue(), timeUnit);
}

if (schedule != null) {
final @Nullable MonitorConfig monitorConfig = new MonitorConfig(schedule);
if (timeZone != null) {
monitorConfig.setTimezone(timeZone.getID());
}
return monitorConfig;
} else {
return null;
}
}

private @Nullable Integer millisToMinutes(final @NotNull Double milis) {
return Double.valueOf((milis / 1000.0) / 60.0).intValue();
}

private @Nullable Integer secondsToMinutes(final @NotNull Double seconds) {
return Double.valueOf(seconds / 60.0).intValue();
}

private @Nullable MonitorScheduleUnit convertUnit(
final @Nullable DateBuilder.IntervalUnit intervalUnit) {
if (intervalUnit == null) {
return null;
}

if (DateBuilder.IntervalUnit.MINUTE.equals(intervalUnit)) {
return MonitorScheduleUnit.MINUTE;
} else if (DateBuilder.IntervalUnit.HOUR.equals(intervalUnit)) {
return MonitorScheduleUnit.HOUR;
} else if (DateBuilder.IntervalUnit.DAY.equals(intervalUnit)) {
return MonitorScheduleUnit.DAY;
} else if (DateBuilder.IntervalUnit.WEEK.equals(intervalUnit)) {
return MonitorScheduleUnit.WEEK;
} else if (DateBuilder.IntervalUnit.MONTH.equals(intervalUnit)) {
return MonitorScheduleUnit.MONTH;
} else if (DateBuilder.IntervalUnit.YEAR.equals(intervalUnit)) {
return MonitorScheduleUnit.YEAR;
}

return null;
}

@Override
public void jobExecutionVetoed(JobExecutionContext context) {
// do nothing
}

@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
try {
final @Nullable Object checkInIdObjectFromContext = context.get(SENTRY_CHECK_IN_ID_KEY);
final @Nullable Object slugObjectFromContext = context.get(SENTRY_CHECK_IN_SLUG_KEY);
final @NotNull SentryId checkInId =
checkInIdObjectFromContext == null
? new SentryId()
: (SentryId) checkInIdObjectFromContext;
final @Nullable String slug =
slugObjectFromContext == null ? null : (String) slugObjectFromContext;
if (slug != null) {
final boolean isFailed = jobException != null;
final @NotNull CheckInStatus status = isFailed ? CheckInStatus.ERROR : CheckInStatus.OK;
Sentry.captureCheckIn(new CheckIn(checkInId, slug, status));
}
} catch (Throwable t) {
Sentry.getCurrentHub()
.getOptions()
.getLogger()
.log(SentryLevel.ERROR, "Unable to capture check-in in jobWasExecuted.", t);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation(Config.Libs.springBoot3StarterWeb)
implementation(Config.Libs.springBoot3StarterWebsocket)
implementation(Config.Libs.springBoot3StarterGraphql)
implementation(Config.Libs.springBoot3StarterQuartz)
implementation(Config.Libs.springBoot3StarterWebflux)
implementation(Config.Libs.springBoot3StarterAop)
implementation(Config.Libs.aspectj)
Expand All @@ -32,6 +33,7 @@ dependencies {
implementation(projects.sentrySpringBootStarterJakarta)
implementation(projects.sentryLogback)
implementation(projects.sentryGraphql)
implementation(projects.sentryQuartz)

// database query tracing
implementation(projects.sentryJdbc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ void execute() throws InterruptedException {
try {
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
Sentry.captureCheckIn(new CheckIn(checkInId, "my_monitor_slug", CheckInStatus.OK));
} catch (Throwable t) {
didError = true;
throw t;
Expand Down

0 comments on commit 20bea77

Please sign in to comment.