Skip to content

Commit 738a9b3

Browse files
fix: Regex for Cron Monitors (#4407)
Resolves #4372 - #4372
1 parent 3c5f76d commit 738a9b3

File tree

3 files changed

+73
-20
lines changed

3 files changed

+73
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
### Fixes
1313

1414
- Update `sample_rate` of _Dynamic Sampling Context (DSC)_ when making sampling decisions ([#4374](https://github.com/getsentry/sentry-dotnet/pull/4374))
15+
- Crontabs now support day names (MON-FRI) and allow step values and ranges to be combined ([#4407](https://github.com/getsentry/sentry-dotnet/pull/4407))
1516

1617
### Dependencies
1718

src/Sentry/SentryMonitorOptions.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,30 +57,32 @@ public partial class SentryMonitorOptions : ISentryJsonSerializable
5757
// - Allows */n for step values where n must be any positive integer (except zero)
5858
// - Allows single values within their valid ranges
5959
// - Allows ranges (e.g., 8-10)
60+
// - Allows step values with ranges (e.g., 8-18/4)
6061
// - Allows lists of values and ranges (e.g., 6,8,9 or 8-10,12-14)
62+
// - Allows weekday names (MON, TUE, WED, THU, FRI, SAT, SUN)
6163
//
6264
// Valid ranges for each field:
6365
// - Minutes: 0-59
6466
// - Hours: 0-23
6567
// - Days: 1-31
6668
// - Months: 1-12
67-
// - Weekdays: 0-7 (0 and 7 both represent Sunday)
68-
private const string ValidCrontabPattern = @"^(\*|(\*\/([1-9][0-9]*))|([0-5]?\d)(-[0-5]?\d)?)(,([0-5]?\d)(-[0-5]?\d)?)*(\s+)(\*|(\*\/([1-9][0-9]*))|([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)((,([01]?\d|2[0-3])(-([01]?\d|2[0-3]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)((,([1-9]|[12]\d|3[01])(-([1-9]|[12]\d|3[01]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|([1-9]|1[0-2])(-([1-9]|1[0-2]))?)((,([1-9]|1[0-2])(-([1-9]|1[0-2]))?)*)?(\s+)(\*|(\*\/([1-9][0-9]*))|[0-7](-[0-7])?)((,[0-7](-[0-7])?)*)?$";
69+
// - Weekdays: 0-7 (0 and 7 both represent Sunday) or MON-SUN
70+
private const string ValidCrontabPattern = @"^(\*(\/([1-9][0-9]*))?|([0-5]?\d)|([0-5]?\d)-([0-5]?\d)(\/([1-9][0-9]*))?)(,(\*(\/([1-9][0-9]*))?|([0-5]?\d)|([0-5]?\d)-([0-5]?\d)(\/([1-9][0-9]*))?))*(\s+)(\*(\/([1-9][0-9]*))?|([01]?\d|2[0-3])|([01]?\d|2[0-3])-([01]?\d|2[0-3])(\/([1-9][0-9]*))?)(,(\*(\/([1-9][0-9]*))?|([01]?\d|2[0-3])|([01]?\d|2[0-3])-([01]?\d|2[0-3])(\/([1-9][0-9]*))?))*(\s+)(\*(\/([1-9][0-9]*))?|([1-9]|[12]\d|3[01])|([1-9]|[12]\d|3[01])-([1-9]|[12]\d|3[01])(\/([1-9][0-9]*))?)(,(\*(\/([1-9][0-9]*))?|([1-9]|[12]\d|3[01])|([1-9]|[12]\d|3[01])-([1-9]|[12]\d|3[01])(\/([1-9][0-9]*))?))*(\s+)(\*(\/([1-9][0-9]*))?|([1-9]|1[0-2])|([1-9]|1[0-2])-([1-9]|1[0-2])(\/([1-9][0-9]*))?)(,(\*(\/([1-9][0-9]*))?|([1-9]|1[0-2])|([1-9]|1[0-2])-([1-9]|1[0-2])(\/([1-9][0-9]*))?))*(\s+)(\*(\/([1-9][0-9]*))?|[0-7]|(MON|TUE|WED|THU|FRI|SAT|SUN)|[0-7]-[0-7](\/([1-9][0-9]*))?|(MON|TUE|WED|THU|FRI|SAT|SUN)-(MON|TUE|WED|THU|FRI|SAT|SUN)(\/([1-9][0-9]*))?)(,(\*(\/([1-9][0-9]*))?|[0-7]|(MON|TUE|WED|THU|FRI|SAT|SUN)|[0-7]-[0-7](\/([1-9][0-9]*))?|(MON|TUE|WED|THU|FRI|SAT|SUN)-(MON|TUE|WED|THU|FRI|SAT|SUN)(\/([1-9][0-9]*))?))*$";
6971

7072
private SentryMonitorScheduleType _type = SentryMonitorScheduleType.None;
7173
private string? _crontab;
7274
private int? _interval;
7375
private SentryMonitorInterval? _unit;
7476

7577
#if NET9_0_OR_GREATER
76-
[GeneratedRegex(ValidCrontabPattern, RegexOptions.IgnoreCase)]
78+
[GeneratedRegex(ValidCrontabPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture)]
7779
private static partial Regex ValidCrontab { get; }
7880
#elif NET8_0
79-
[GeneratedRegex(ValidCrontabPattern, RegexOptions.IgnoreCase)]
81+
[GeneratedRegex(ValidCrontabPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture)]
8082
private static partial Regex ValidCrontabRegex();
8183
private static readonly Regex ValidCrontab = ValidCrontabRegex();
8284
#else
83-
private static readonly Regex ValidCrontab = new(ValidCrontabPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);
85+
private static readonly Regex ValidCrontab = new(ValidCrontabPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
8486
#endif
8587

8688
/// <summary>

test/Sentry.Tests/SentryMonitorOptionsTests.cs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ day of month 1-31
1919
/ step values
2020
*/
2121
[Theory]
22-
[InlineData("* * * * *")]
23-
[InlineData("0 0 1 1 *")]
24-
[InlineData("0 0 1 * 0")]
25-
[InlineData("59 23 31 12 7")]
26-
[InlineData("0 */2 * * *")]
27-
[InlineData("0 8-10 * * *")]
28-
[InlineData("0 6,8,9 * * *")]
22+
[InlineData("* * * * *")] // Every minute
23+
[InlineData("0 0 1 1 *")] // At midnight on January 1st
24+
[InlineData("0 0 1 * 0")] // At midnight on the first day of the month if it's Sunday
25+
[InlineData("59 23 31 12 7")] // At 23:59 on December 31st if it's Sunday
26+
[InlineData("0 */2 * * *")] // Every 2 hours
27+
[InlineData("0 8-10 * * *")] // At 8, 9, and 10 AM every day
28+
[InlineData("0 6,8,9 * * *")] // At 6, 8, and 9 AM every day
2929
// Step values (*/n)
3030
[InlineData("*/15 * * * *")] // Every 15 minutes
3131
[InlineData("0 */6 * * *")] // Every 6 hours
@@ -53,6 +53,34 @@ day of month 1-31
5353
[InlineData("0 0 1 1 0")] // Minimum values
5454
[InlineData("*/1 * * * *")] // Step of 1
5555
[InlineData("* * 31 */2 *")] // 31st of every other month
56+
// Weekday names
57+
[InlineData("0 0 * * MON")] // Monday only (weekday name)
58+
[InlineData("0 9 * * MON-FRI")] // 9 AM, Monday to Friday (weekday range)
59+
[InlineData("0 18 * * MON-FRI")] // 6 PM, Monday to Friday (weekday range)
60+
[InlineData("0 0 * * MON-FRI")] // Midnight, Monday to Friday (weekday range)
61+
[InlineData("0 20 * * MON-FRI")] // 8 PM, Monday to Friday (weekday range)
62+
// Step values with ranges
63+
[InlineData("0-30/15 * * * *")] // Every 15 minutes from 0-30
64+
[InlineData("0-59/10 * * * *")] // Every 10 minutes from 0-59
65+
[InlineData("0-45/5 * * * *")] // Every 5 minutes from 0-45
66+
[InlineData("* 8-18/2 * * *")] // Every 2 hours from 8-18
67+
[InlineData("* 0-23/6 * * *")] // Every 6 hours from 0-23
68+
[InlineData("* 9-17/1 * * *")] // Every hour from 9-17
69+
[InlineData("* * 1-15/3 * *")] // Every 3 days from 1-15
70+
[InlineData("* * 1-31/7 * *")] // Every 7 days from 1-31
71+
[InlineData("* * 10-20/2 * *")] // Every 2 days from 10-20
72+
[InlineData("* * * 1-6/2 *")] // Every 2 months from January-June
73+
[InlineData("* * * 1-12/3 *")] // Every 3 months from January-December
74+
[InlineData("* * * 3-9/1 *")] // Every month from March-September
75+
[InlineData("* * * * 1-5/2")] // Every 2 weekdays from 1-5 (Mon-Fri)
76+
[InlineData("* * * * 0-6/3")] // Every 3 days of week from (Sun-Sat)
77+
[InlineData("* * * * MON-FRI/2")] // Every 2 weekdays from Mon-Fri
78+
[InlineData("* * * * MON-SUN/3")] // Every 3 days from Mon-Sun
79+
// Complex combinations with step values and ranges
80+
[InlineData("0-30/15 8-18/2 * * *")] // Every 15 min from 0-30, every 2 hours from 8-18
81+
[InlineData("0-45/5 9-17/1 1-15/3 * *")] // Complex combination
82+
[InlineData("*/10 8-18/4 1-31/7 1-12/3 MON-FRI/2")] // All fields with step values and ranges
83+
[InlineData("1-2/3 * * * *")] // Step value larger than range
5684
public void Interval_ValidCrontab_DoesNotThrow(string crontab)
5785
{
5886
// Arrange
@@ -74,14 +102,14 @@ public void Interval_SetMoreThanOnce_Throws()
74102
}
75103

76104
[Theory]
77-
[InlineData("")]
78-
[InlineData("not a crontab")]
79-
[InlineData("* * a * *")]
80-
[InlineData("60 * * * *")]
81-
[InlineData("* 24 * * *")]
82-
[InlineData("* * 32 * *")]
83-
[InlineData("* * * 13 *")]
84-
[InlineData("* * * * 8")]
105+
[InlineData("")] // Empty string
106+
[InlineData("not a crontab")] // Not a valid crontab format
107+
[InlineData("* * a * *")] // Invalid character for day-of-month
108+
[InlineData("60 * * * *")] // Minute value exceeds 59
109+
[InlineData("* 24 * * *")] // Hour value exceeds 23
110+
[InlineData("* * 32 * *")] // Day of month value exceeds 31
111+
[InlineData("* * * 13 *")] // Month value exceeds 12
112+
[InlineData("* * * * 8")] // Day of week value exceeds 7
85113
// Invalid step values
86114
[InlineData("*/0 * * * *")] // Step value cannot be 0
87115
// Invalid ranges
@@ -101,6 +129,28 @@ public void Interval_SetMoreThanOnce_Throws()
101129
[InlineData("*/* * * * *")] // Invalid step format
102130
[InlineData(",1,2 * * * *")] // Leading comma
103131
[InlineData("1,2, * * * *")] // Trailing comma
132+
// Invalid step values with ranges
133+
[InlineData("0-60/15 * * * *")] // Minute range exceeds 59
134+
[InlineData("0-30/0 * * * *")] // Step value cannot be 0
135+
[InlineData("0-30/-5 * * * *")] // Negative step value
136+
[InlineData("* 8-25/2 * * *")] // Hour range exceeds 23
137+
[InlineData("* 8-18/0 * * *")] // Step value cannot be 0
138+
[InlineData("* * 0-31/3 * *")] // Day cannot be 0
139+
[InlineData("* * 1-32/3 * *")] // Day range exceeds 31
140+
[InlineData("* * * 0-12/2 *")] // Month cannot be 0
141+
[InlineData("* * * 1-13/2 *")] // Month range exceeds 12
142+
[InlineData("* * * * 0-8/2")] // Weekday range exceeds 7
143+
[InlineData("* * * * MON-FRI/0")] // Step value cannot be 0
144+
[InlineData("0-30//15 * * * *")] // Double slash
145+
[InlineData("0-30/15/ * * * *")] // Trailing slash
146+
[InlineData("0-30/15- * * * *")] // Incomplete range
147+
// Invalid single value with step (should only allow step with * or ranges)
148+
[InlineData("30/5 * * * *")] // Single minute with step
149+
[InlineData("* 8/2 * * *")] // Single hour with step
150+
[InlineData("* * 15/3 * *")] // Single day with step
151+
[InlineData("* * * 6/2 *")] // Single month with step
152+
[InlineData("* * * * 3/2")] // Single weekday with step
153+
[InlineData("* * * * MON/2")] // Single weekday name with step
104154
public void CaptureCheckIn_InvalidCrontabSet_Throws(string crontab)
105155
{
106156
// Arrange

0 commit comments

Comments
 (0)