Skip to content

Commit c9a79bf

Browse files
authored
Improvements around the cron monitoring feature (#2212)
* Test MonitorCheckIns end-to-end * Apply global cron configs outside of MonitorConfig class This has a few benefits: - Keep the MonitorConfig class simple - Make check in logic centralized in MonitorCheckIns module * Make MonitorCheckIns' rescue more precise * Remove unnecessary extend
1 parent 1fe52d1 commit c9a79bf

File tree

4 files changed

+159
-115
lines changed

4 files changed

+159
-115
lines changed

sentry-ruby/lib/sentry/cron/monitor_check_ins.rb

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,42 @@ def perform(*args, **opts)
1313
monitor_config: monitor_config)
1414

1515
start = Sentry.utc_now.to_i
16-
# need to do this on ruby <= 2.6 sadly
17-
ret = method(:perform).super_method.arity == 0 ? super() : super
18-
duration = Sentry.utc_now.to_i - start
1916

20-
Sentry.capture_check_in(slug,
21-
:ok,
22-
check_in_id: check_in_id,
23-
duration: duration,
24-
monitor_config: monitor_config)
17+
begin
18+
# need to do this on ruby <= 2.6 sadly
19+
ret = method(:perform).super_method.arity == 0 ? super() : super
20+
duration = Sentry.utc_now.to_i - start
2521

26-
ret
27-
rescue Exception
28-
duration = Sentry.utc_now.to_i - start
22+
Sentry.capture_check_in(slug,
23+
:ok,
24+
check_in_id: check_in_id,
25+
duration: duration,
26+
monitor_config: monitor_config)
2927

30-
Sentry.capture_check_in(slug,
31-
:error,
32-
check_in_id: check_in_id,
33-
duration: duration,
34-
monitor_config: monitor_config)
28+
ret
29+
rescue Exception
30+
duration = Sentry.utc_now.to_i - start
3531

36-
raise
32+
Sentry.capture_check_in(slug,
33+
:error,
34+
check_in_id: check_in_id,
35+
duration: duration,
36+
monitor_config: monitor_config)
37+
38+
raise
39+
end
3740
end
3841
end
3942

4043
module ClassMethods
4144
def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
45+
if monitor_config && Sentry.configuration
46+
cron_config = Sentry.configuration.cron
47+
monitor_config.checkin_margin ||= cron_config.default_checkin_margin
48+
monitor_config.max_runtime ||= cron_config.default_max_runtime
49+
monitor_config.timezone ||= cron_config.default_timezone
50+
end
51+
4252
@sentry_monitor_slug = slug
4353
@sentry_monitor_config = monitor_config
4454

@@ -57,8 +67,6 @@ def sentry_monitor_config
5767
end
5868
end
5969

60-
extend ClassMethods
61-
6270
def self.included(base)
6371
base.extend(ClassMethods)
6472
end

sentry-ruby/lib/sentry/cron/monitor_config.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class MonitorConfig
2525

2626
def initialize(schedule, checkin_margin: nil, max_runtime: nil, timezone: nil)
2727
@schedule = schedule
28-
@checkin_margin = checkin_margin || Sentry.configuration&.cron&.default_checkin_margin
29-
@max_runtime = max_runtime || Sentry.configuration&.cron&.default_max_runtime
30-
@timezone = timezone || Sentry.configuration&.cron&.default_timezone
28+
@checkin_margin = checkin_margin
29+
@max_runtime = max_runtime
30+
@timezone = timezone
3131
end
3232

3333
def self.from_crontab(crontab, **options)

sentry-ruby/spec/sentry/cron/monitor_check_ins_spec.rb

Lines changed: 128 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ def perform(a, b = 42, c: 99)
3333
it_behaves_like 'original_job'
3434

3535
it 'does not call capture_check_in' do
36-
expect(Sentry).not_to receive(:capture_check_in)
3736
job.perform(1)
37+
38+
expect(sentry_events.count).to eq(0)
3839
end
3940
end
4041

@@ -105,20 +106,21 @@ def perform(a, b = 42, c: 99)
105106
expect(Job.ancestors.first).to eq(described_class::Patch)
106107
end
107108

108-
it 'calls capture_check_in twice' do
109-
expect(Sentry).to receive(:capture_check_in).with(
110-
'job',
111-
:in_progress,
112-
hash_including(monitor_config: nil)
113-
).ordered.and_call_original
109+
it 'records 2 check-in events' do
110+
job.perform(1)
111+
112+
expect(sentry_events.count).to eq(2)
113+
in_progress_event = sentry_events.first
114114

115-
expect(Sentry).to receive(:capture_check_in).with(
116-
'job',
117-
:ok,
118-
hash_including(:check_in_id, monitor_config: nil, duration: 0)
119-
).ordered.and_call_original
115+
expect(in_progress_event.monitor_slug).to eq('job')
116+
expect(in_progress_event.status).to eq(:in_progress)
117+
expect(in_progress_event.monitor_config).to be_nil
120118

121-
job.perform(1)
119+
ok_event = sentry_events.last
120+
121+
expect(ok_event.monitor_slug).to eq('job')
122+
expect(ok_event.status).to eq(:ok)
123+
expect(ok_event.monitor_config).to be_nil
122124
end
123125
end
124126

@@ -143,25 +145,21 @@ def perform; work end
143145
expect(Job.ancestors.first).to eq(described_class::Patch)
144146
end
145147

146-
it 'does the work' do
147-
expect(job).to receive(:work).and_call_original
148-
expect(job.perform).to eq(42)
149-
end
148+
it 'records 2 check-in events' do
149+
expect(job.perform(1)).to eq(42)
150150

151-
it 'calls capture_check_in twice' do
152-
expect(Sentry).to receive(:capture_check_in).with(
153-
'job',
154-
:in_progress,
155-
hash_including(monitor_config: nil)
156-
).ordered.and_call_original
151+
expect(sentry_events.count).to eq(2)
152+
in_progress_event = sentry_events.first
157153

158-
expect(Sentry).to receive(:capture_check_in).with(
159-
'job',
160-
:ok,
161-
hash_including(:check_in_id, monitor_config: nil, duration: 0)
162-
).ordered.and_call_original
154+
expect(in_progress_event.monitor_slug).to eq('job')
155+
expect(in_progress_event.status).to eq(:in_progress)
156+
expect(in_progress_event.monitor_config).to be_nil
163157

164-
job.perform(1)
158+
ok_event = sentry_events.last
159+
160+
expect(ok_event.monitor_slug).to eq('job')
161+
expect(ok_event.status).to eq(:ok)
162+
expect(ok_event.monitor_config).to be_nil
165163
end
166164
end
167165

@@ -174,10 +172,7 @@ def perform; work end
174172

175173
sentry_monitor_check_ins
176174

177-
def work(a, b, c); a + b + c end
178-
179-
def perform(a, b = 42, c: 99)
180-
work(a, b, c)
175+
def perform
181176
end
182177
end
183178

@@ -190,17 +185,17 @@ def perform(a, b = 42, c: 99)
190185
end
191186
end
192187

193-
context 'patched with custom options' do
194-
let(:config) { Sentry::Cron::MonitorConfig::from_interval(1, :minute) }
188+
context 'patched with monitor config' do
189+
let(:monitor_config) { Sentry::Cron::MonitorConfig::from_interval(1, :minute) }
195190

196191
before do
197192
mod = described_class
198-
conf = config
193+
config = monitor_config
199194

200195
job_class = Class.new do
201196
include mod
202197

203-
sentry_monitor_check_ins slug: 'custom_slug', monitor_config: conf
198+
sentry_monitor_check_ins slug: 'custom_slug', monitor_config: config
204199

205200
def work(a, b, c); a + b + c end
206201

@@ -222,37 +217,104 @@ def perform(a, b = 42, c: 99)
222217

223218
it 'has correct custom options' do
224219
expect(Job.sentry_monitor_slug).to eq('custom_slug')
225-
expect(Job.sentry_monitor_config).to eq(config)
220+
expect(Job.sentry_monitor_config).to eq(monitor_config)
221+
end
222+
223+
it 'records 2 check-in events' do
224+
job.perform(1)
225+
226+
expect(sentry_events.count).to eq(2)
227+
in_progress_event = sentry_events.first
228+
229+
expect(in_progress_event.monitor_slug).to eq('custom_slug')
230+
expect(in_progress_event.status).to eq(:in_progress)
231+
expect(in_progress_event.monitor_config).to eq(monitor_config)
232+
expect(in_progress_event.monitor_config.checkin_margin).to eq(nil)
233+
expect(in_progress_event.monitor_config.max_runtime).to eq(nil)
234+
expect(in_progress_event.monitor_config.timezone).to eq(nil)
235+
236+
ok_event = sentry_events.last
237+
238+
expect(ok_event.monitor_slug).to eq('custom_slug')
239+
expect(ok_event.status).to eq(:ok)
240+
expect(ok_event.monitor_config).to eq(monitor_config)
241+
end
242+
end
243+
244+
context 'with custom monitor config object and cron configs' do
245+
let(:monitor_config) { Sentry::Cron::MonitorConfig::from_interval(1, :minute) }
246+
247+
before do
248+
perform_basic_setup do |config|
249+
config.cron.default_checkin_margin = 10
250+
config.cron.default_max_runtime = 20
251+
config.cron.default_timezone = 'Europe/Vienna'
252+
end
253+
254+
mod = described_class
255+
config = monitor_config
256+
257+
job_class = Class.new do
258+
include mod
259+
260+
sentry_monitor_check_ins slug: 'custom_slug', monitor_config: config
261+
262+
def work(a, b, c); a + b + c end
263+
264+
def perform(a, b = 42, c: 99)
265+
work(a, b, c)
266+
end
267+
end
268+
269+
stub_const('Job', job_class)
226270
end
227271

228-
it 'calls capture_check_in twice' do
229-
expect(Sentry).to receive(:capture_check_in).with(
230-
'custom_slug',
231-
:in_progress,
232-
hash_including(monitor_config: config)
233-
).ordered.and_call_original
272+
let(:job) { Job.new }
273+
274+
it_behaves_like 'original_job'
275+
276+
it 'prepends the patch' do
277+
expect(Job.ancestors.first).to eq(described_class::Patch)
278+
end
234279

235-
expect(Sentry).to receive(:capture_check_in).with(
236-
'custom_slug',
237-
:ok,
238-
hash_including(:check_in_id, monitor_config: config, duration: 0)
239-
).ordered.and_call_original
280+
it 'has correct custom options' do
281+
expect(Job.sentry_monitor_slug).to eq('custom_slug')
282+
expect(Job.sentry_monitor_config).to eq(monitor_config)
283+
end
240284

285+
it 'records 2 check-in events' do
241286
job.perform(1)
287+
288+
expect(sentry_events.count).to eq(2)
289+
in_progress_event = sentry_events.first
290+
291+
expect(in_progress_event.monitor_slug).to eq('custom_slug')
292+
expect(in_progress_event.status).to eq(:in_progress)
293+
expect(in_progress_event.monitor_config.checkin_margin).to eq(10)
294+
expect(in_progress_event.monitor_config.max_runtime).to eq(20)
295+
expect(in_progress_event.monitor_config.timezone).to eq('Europe/Vienna')
296+
297+
ok_event = sentry_events.last
298+
299+
expect(ok_event.monitor_slug).to eq('custom_slug')
300+
expect(ok_event.status).to eq(:ok)
301+
expect(ok_event.monitor_config.checkin_margin).to eq(10)
302+
expect(ok_event.monitor_config.max_runtime).to eq(20)
303+
expect(ok_event.monitor_config.timezone).to eq('Europe/Vienna')
242304
end
243305
end
244306

245307
context 'patched with custom options with exception' do
246-
let(:config) { Sentry::Cron::MonitorConfig::from_crontab('5 * * * *') }
308+
let(:monitor_config) { Sentry::Cron::MonitorConfig::from_crontab('5 * * * *') }
247309

248310
before do
249311
mod = described_class
250-
conf = config
312+
config = monitor_config
251313

252314
job_class = Class.new do
253315
include mod
254316

255-
sentry_monitor_check_ins slug: 'custom_slug', monitor_config: conf
317+
sentry_monitor_check_ins slug: 'custom_slug', monitor_config: config
256318

257319
def work(a, b, c);
258320
1 / 0
@@ -284,23 +346,24 @@ def perform(a, b = 42, c: 99)
284346

285347
it 'has correct custom options' do
286348
expect(Job.sentry_monitor_slug).to eq('custom_slug')
287-
expect(Job.sentry_monitor_config).to eq(config)
349+
expect(Job.sentry_monitor_config).to eq(monitor_config)
288350
end
289351

290352
it 'calls capture_check_in twice with error status and re-raises exception' do
291-
expect(Sentry).to receive(:capture_check_in).with(
292-
'custom_slug',
293-
:in_progress,
294-
hash_including(monitor_config: config)
295-
).ordered.and_call_original
296-
297-
expect(Sentry).to receive(:capture_check_in).with(
298-
'custom_slug',
299-
:error,
300-
hash_including(:check_in_id, monitor_config: config, duration: 0)
301-
).ordered.and_call_original
302-
303353
expect { job.perform(1) }.to raise_error(ZeroDivisionError)
354+
355+
expect(sentry_events.count).to eq(2)
356+
in_progress_event = sentry_events.first
357+
358+
expect(in_progress_event.monitor_slug).to eq('custom_slug')
359+
expect(in_progress_event.status).to eq(:in_progress)
360+
expect(in_progress_event.monitor_config).to eq(monitor_config)
361+
362+
error_event = sentry_events.last
363+
364+
expect(error_event.monitor_slug).to eq('custom_slug')
365+
expect(error_event.status).to eq(:error)
366+
expect(error_event.monitor_config).to eq(monitor_config)
304367
end
305368
end
306369
end

0 commit comments

Comments
 (0)