Skip to content

Commit bbdbf02

Browse files
committed
Merge branch 'danf/monitor-config-utils' into danf/spring-checkin-scheduled-config
2 parents 933e34c + 780c605 commit bbdbf02

3 files changed

Lines changed: 178 additions & 67 deletions

File tree

‎sentry/api/sentry.api‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8121,8 +8121,8 @@ public final class io/sentry/util/MapObjectWriter : io/sentry/ObjectWriter {
81218121
}
81228122

81238123
public final class io/sentry/util/MonitorConfigUtils {
8124-
public static fun fromSchedule (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)Lio/sentry/MonitorConfig;
8125-
public static fun fromSpringScheduled (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)Lio/sentry/MonitorConfig;
8124+
public static fun fromSchedule (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lio/sentry/MonitorConfig;
8125+
public static fun fromSpringScheduled (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Z)Lio/sentry/MonitorConfig;
81268126
public static fun parsePeriodMillis (Ljava/lang/String;Ljava/util/concurrent/TimeUnit;)Ljava/lang/Long;
81278127
}
81288128

‎sentry/src/main/java/io/sentry/util/MonitorConfigUtils.java‎

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,20 @@ public final class MonitorConfigUtils {
7272
private MonitorConfigUtils() {}
7373

7474
/**
75-
* Builds a monitor config from exactly one of a cron, fixed rate or fixed delay.
75+
* Builds a monitor config from exactly one of a cron or a fixed interval.
7676
*
7777
* @param cron 6 field cron (seconds first) or macro, null or empty if unset
7878
* @param zone cron time zone, ignored for intervals
79-
* @param fixedRateMillis null if unset
80-
* @param fixedDelayMillis null if unset
79+
* @param intervalMillis fixed rate, null if unset
8180
* @return null if the schedule can't be expressed as a Sentry monitor schedule
8281
*/
8382
public static @Nullable MonitorConfig fromSchedule(
8483
final @Nullable String cron,
8584
final @Nullable String zone,
86-
final @Nullable Long fixedRateMillis,
87-
final @Nullable Long fixedDelayMillis) {
85+
final @Nullable Long intervalMillis) {
8886
final boolean hasCron = cron != null && !cron.isEmpty();
8987

90-
if (hasCron && cron != null && fixedRateMillis == null && fixedDelayMillis == null) {
88+
if (hasCron && cron != null && intervalMillis == null) {
9189
final @Nullable String crontab = toCrontab(cron);
9290
if (crontab == null) {
9391
return null;
@@ -103,9 +101,8 @@ private MonitorConfigUtils() {}
103101
return config;
104102
}
105103

106-
final @Nullable Long period = fixedRateMillis != null ? fixedRateMillis : fixedDelayMillis;
107-
if (!hasCron && period != null && (fixedRateMillis == null || fixedDelayMillis == null)) {
108-
final long millis = period;
104+
if (!hasCron && intervalMillis != null) {
105+
final long millis = intervalMillis;
109106
if (millis < MINUTE_MILLIS || millis % MINUTE_MILLIS != 0) {
110107
return null;
111108
}
@@ -128,30 +125,72 @@ private MonitorConfigUtils() {}
128125

129126
/**
130127
* Builds a monitor config from Spring {@code @Scheduled} values with placeholders resolved. Reads
131-
* days of week and a cron without zone the way Spring does.
128+
* the cron and a cron without zone the way Spring does.
132129
*
133130
* @param cron Spring cron, null or empty if unset
134131
* @param zone cron time zone, null or empty for the JVM default zone
135132
* @param fixedRateMillis null if unset
136-
* @param fixedDelayMillis null if unset
133+
* @param fixedDelayMillis null if unset; a fixed delay never gets a config
134+
* @param legacyCronParser true for Spring before 5.3, which parses crons with {@code
135+
* CronSequenceGenerator}
137136
* @return null if the schedule can't be expressed as a Sentry monitor schedule
138137
*/
139138
public static @Nullable MonitorConfig fromSpringScheduled(
140139
final @Nullable String cron,
141140
final @Nullable String zone,
142141
final @Nullable Long fixedRateMillis,
143-
final @Nullable Long fixedDelayMillis) {
144-
final @Nullable String crontabCron = toCrontabDaysOfWeek(cron);
142+
final @Nullable Long fixedDelayMillis,
143+
final boolean legacyCronParser) {
144+
// runs start a delay after the previous run ends, so they drift from any interval
145+
if (fixedDelayMillis != null) {
146+
return null;
147+
}
148+
final @Nullable String crontabCron =
149+
legacyCronParser ? toCrontabLegacy(cron) : toCrontabDaysOfWeek(cron);
145150
@Nullable String cronZone = null;
146151
if (crontabCron != null && !crontabCron.isEmpty()) {
147152
cronZone = zone == null || zone.isEmpty() ? TimeZone.getDefault().getID() : zone;
148153
}
149-
return fromSchedule(crontabCron, cronZone, fixedRateMillis, fixedDelayMillis);
154+
return fromSchedule(crontabCron, cronZone, fixedRateMillis);
155+
}
156+
157+
/**
158+
* Spring before 5.3 steps day of month {@code *}/n from 0, so it runs on n, 2n, ... Days of week
159+
* already match crontab. Null if both day fields are set, as it then sometimes runs at midnight.
160+
*/
161+
static @Nullable String toCrontabLegacy(final @Nullable String cron) {
162+
if (cron == null) {
163+
return null;
164+
}
165+
final @NotNull String[] fields = cron.trim().split("\\s+", -1);
166+
if (fields.length != 6) {
167+
return cron;
168+
}
169+
if (!isAnyDay(fields[3]) && !isAnyDay(fields[5])) {
170+
return null;
171+
}
172+
final @NotNull StringBuilder daysOfMonth = new StringBuilder();
173+
for (@NotNull String item : fields[3].split(",", -1)) {
174+
if (item.startsWith("*/")) {
175+
item = item.substring(2) + "-31" + item.substring(1);
176+
}
177+
if (daysOfMonth.length() > 0) {
178+
daysOfMonth.append(',');
179+
}
180+
daysOfMonth.append(item);
181+
}
182+
fields[3] = daysOfMonth.toString();
183+
return join(fields);
184+
}
185+
186+
private static boolean isAnyDay(final @NotNull String field) {
187+
return "*".equals(field) || "?".equals(field);
150188
}
151189

152190
/**
153191
* Spring numbers days of week from Monday with 0 or 7 for Sunday, and starts {@code *} on Monday.
154-
* Rewrites them so crontab reads them the same way.
192+
* Rewrites them so crontab reads them the same way. Null for {@code #5}, which Spring 5.3 also
193+
* runs in months without a fifth weekday.
155194
*/
156195
static @Nullable String toCrontabDaysOfWeek(final @Nullable String cron) {
157196
if (cron == null) {
@@ -166,6 +205,9 @@ private MonitorConfigUtils() {}
166205
for (int i = 0; i < DAY_NAMES.size(); i++) {
167206
item = item.replace(DAY_NAMES.get(i), String.valueOf(i == 0 ? 7 : i));
168207
}
208+
if (item.matches(".*#0*5")) {
209+
return null;
210+
}
169211
if (item.startsWith("*/")) {
170212
item = "1-7" + item.substring(1);
171213
} else if (item.startsWith("7-")) {
@@ -178,6 +220,10 @@ private MonitorConfigUtils() {}
178220
daysOfWeek.append(item);
179221
}
180222
fields[5] = daysOfWeek.toString();
223+
return join(fields);
224+
}
225+
226+
private static @NotNull String join(final @NotNull String[] fields) {
181227
final @NotNull StringBuilder result = new StringBuilder();
182228
for (int i = 0; i < fields.length; i++) {
183229
if (i > 0) {
@@ -370,6 +416,10 @@ private MonitorConfigUtils() {}
370416
return null;
371417
}
372418
if (items.size() == 1) {
419+
// cronsim steps a single value range like 10-10/2 to the field max
420+
if (base.indexOf('-') >= 0) {
421+
return null;
422+
}
373423
return range(items.iterator().next(), FIELD_MAX[index], step);
374424
}
375425
final @NotNull List<Integer> sorted = new ArrayList<>(items);

0 commit comments

Comments
 (0)