Skip to content

Commit

Permalink
Add Sentry::Cron::MonitorCheckIns module for automatic monitoring o…
Browse files Browse the repository at this point in the history
…f jobs (#2130)

Standard job frameworks such as `ActiveJob` and `Sidekiq` can now use this module to automatically capture check ins.

```rb
class ExampleJob < ApplicationJob
  include Sentry::Cron::MonitorCheckIns

  sentry_monitor_check_ins

  def perform(*args)
    # do stuff
  end
end
```

```rb
class SidekiqJob
  include Sidekiq::Job
  include Sentry::Cron::MonitorCheckIns

  sentry_monitor_check_ins

  def perform(*args)
    # do stuff
  end
end
```

You can pass in optional attributes to `sentry_monitor_check_ins` as follows.
```rb
sentry_monitor_check_ins slug: 'custom_slug'

sentry_monitor_check_ins monitor_config: Sentry::Cron::MonitorConfig.from_interval(1, :minute)

sentry_monitor_check_ins monitor_config: Sentry::Cron::MonitorConfig.from_crontab('5 * * * *')
```
  • Loading branch information
sl0thentr0py authored Oct 9, 2023
1 parent 3d0ed07 commit 65ef04c
Show file tree
Hide file tree
Showing 7 changed files with 484 additions and 8 deletions.
55 changes: 48 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,56 @@

- Record client reports for profiles [#2107](https://github.com/getsentry/sentry-ruby/pull/2107)
- Adopt Rails 7.1's new BroadcastLogger [#2120](https://github.com/getsentry/sentry-ruby/pull/2120)
- Add `Sentry.capture_check_in` API for Cron Monitoring [#2117](https://github.com/getsentry/sentry-ruby/pull/2117)
- Add [Cron Monitoring](https://docs.sentry.io/product/crons/) support
- Add `Sentry.capture_check_in` API for Cron Monitoring [#2117](https://github.com/getsentry/sentry-ruby/pull/2117)

You can now track progress of long running scheduled jobs.
You can now track progress of long running scheduled jobs.

```rb
check_in_id = Sentry.capture_check_in('job_name', :in_progress)
# do job stuff
Sentry.capture_check_in('job_name', :ok, check_in_id: check_in_id)
```
```rb
check_in_id = Sentry.capture_check_in('job_name', :in_progress)
# do job stuff
Sentry.capture_check_in('job_name', :ok, check_in_id: check_in_id)
```
- Add `Sentry::Cron::MonitorCheckIns` module for automatic monitoring of jobs [#2130](https://github.com/getsentry/sentry-ruby/pull/2130)

Standard job frameworks such as `ActiveJob` and `Sidekiq` can now use this module to automatically capture check ins.

```rb
class ExampleJob < ApplicationJob
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins
def perform(*args)
# do stuff
end
end
```

```rb
class SidekiqJob
include Sidekiq::Job
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins
def perform(*args)
# do stuff
end
end
```

You can pass in optional attributes to `sentry_monitor_check_ins` as follows.
```rb
# slug defaults to the job class name
sentry_monitor_check_ins slug: 'custom_slug'
# define the monitor config with an interval
sentry_monitor_check_ins monitor_config: Sentry::Cron::MonitorConfig.from_interval(1, :minute)
# define the monitor config with a crontab
sentry_monitor_check_ins monitor_config: Sentry::Cron::MonitorConfig.from_crontab('5 * * * *')
```

### Bug Fixes

Expand Down
75 changes: 75 additions & 0 deletions sentry-rails/spec/sentry/rails/activejob_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ def rescue_callback(error)
end
end

class NormalJobWithCron < NormalJob
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins
end

class FailedJobWithCron < FailedJob
include Sentry::Cron::MonitorCheckIns
sentry_monitor_check_ins slug: "failed_job", monitor_config: Sentry::Cron::MonitorConfig.from_crontab("5 * * * *")
end


RSpec.describe "without Sentry initialized" do
it "runs job" do
expect { FailedJob.perform_now }.to raise_error(FailedJob::TestError)
Expand Down Expand Up @@ -311,4 +322,68 @@ def perform(event, hint)
end
end
end

context "with cron monitoring mixin" do
context "normal job" do
it "returns #perform method's return value" do
expect(NormalJobWithCron.perform_now).to eq("foo")
end

it "captures two check ins" do
NormalJobWithCron.perform_now

expect(transport.events.size).to eq(2)

first = transport.events[0]
check_in_id = first.check_in_id
expect(first).to be_a(Sentry::CheckInEvent)
expect(first.to_hash).to include(
type: 'check_in',
check_in_id: check_in_id,
monitor_slug: "NormalJobWithCron",
status: :in_progress
)

second = transport.events[1]
expect(second).to be_a(Sentry::CheckInEvent)
expect(second.to_hash).to include(
:duration,
type: 'check_in',
check_in_id: check_in_id,
monitor_slug: "NormalJobWithCron",
status: :ok
)
end
end

context "failed job" do
it "captures two check ins" do
expect { FailedJobWithCron.perform_now }.to raise_error(FailedJob::TestError)

expect(transport.events.size).to eq(3)

first = transport.events[0]
check_in_id = first.check_in_id
expect(first).to be_a(Sentry::CheckInEvent)
expect(first.to_hash).to include(
type: 'check_in',
check_in_id: check_in_id,
monitor_slug: "failed_job",
status: :in_progress,
monitor_config: { schedule: { type: :crontab, value: "5 * * * *" } }
)

second = transport.events[1]
expect(second).to be_a(Sentry::CheckInEvent)
expect(second.to_hash).to include(
:duration,
type: 'check_in',
check_in_id: check_in_id,
monitor_slug: "failed_job",
status: :error,
monitor_config: { schedule: { type: :crontab, value: "5 * * * *" } }
)
end
end
end
end
1 change: 1 addition & 0 deletions sentry-ruby/lib/sentry-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require "sentry/hub"
require "sentry/background_worker"
require "sentry/session_flusher"
require "sentry/cron/monitor_check_ins"

[
"sentry/rake",
Expand Down
61 changes: 61 additions & 0 deletions sentry-ruby/lib/sentry/cron/monitor_check_ins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module Sentry
module Cron
module MonitorCheckIns
module Patch
def perform(*args)
slug = self.class.sentry_monitor_slug || self.class.name
monitor_config = self.class.sentry_monitor_config

check_in_id = Sentry.capture_check_in(slug,
:in_progress,
monitor_config: monitor_config)

start = Sentry.utc_now.to_i
ret = super
duration = Sentry.utc_now.to_i - start

Sentry.capture_check_in(slug,
:ok,
check_in_id: check_in_id,
duration: duration,
monitor_config: monitor_config)

ret
rescue Exception
duration = Sentry.utc_now.to_i - start

Sentry.capture_check_in(slug,
:error,
check_in_id: check_in_id,
duration: duration,
monitor_config: monitor_config)

raise
end
end

module ClassMethods
def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
@sentry_monitor_slug = slug
@sentry_monitor_config = monitor_config

prepend Patch
end

def sentry_monitor_slug
@sentry_monitor_slug
end

def sentry_monitor_config
@sentry_monitor_config
end
end

extend ClassMethods

def self.included(base)
base.extend(ClassMethods)
end
end
end
end
Loading

0 comments on commit 65ef04c

Please sign in to comment.