Skip to content

Commit 9303b9a

Browse files
authored
Use prepended method instead of around_perform for ActiveJob integration (#1631)
* use prepended method instead of around_perform for activejob integration since the sdk registers its around_perform as the "first" callback, it needs to call all error handlers manually (which naturally should happen later) to see if it should report the exception. usually, this approach works well because if an exception will be handled later, handling it ealier doesn't make much of a difference. however, as described in #956, if there's another exception happened inside one of the error handlers, it'll re-enter the error handler again in here: https://github.com/rails/rails/blob/38cb5610b1/activejob/lib/active_job/execution.rb#l50-l51 Of course, it may be possible to handle such case by adding more rescue block inside the `capture_and_reraise_with_sentry` method. But it'll force the entire exception handling flow to be performed inside the first `around_perform` block. And that's something we should avoid. * Refactor ActiveJob integration * Fix SendEventJob's rescue_from callback * Update changelog
1 parent 27da65b commit 9303b9a

File tree

4 files changed

+61
-35
lines changed

4 files changed

+61
-35
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## Unreleased
2+
3+
### Bug Fixes
4+
5+
- Use prepended method instead of `around_perform` for `ActiveJob` integration [#1631](https://github.com/getsentry/sentry-ruby/pull/1631)
6+
- Fixes [#956](https://github.com/getsentry/sentry-ruby/issues/956) and [#1629](https://github.com/getsentry/sentry-ruby/issues/1629)
7+
18
## 4.8.1
29

310
### Bug Fixes

sentry-rails/app/jobs/sentry/send_event_job.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class SendEventJob < parent_job
1616
discard_on ActiveJob::DeserializationError
1717
else
1818
# mimic what discard_on does for Rails 5.0
19-
rescue_from ActiveJob::DeserializationError do
20-
logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
19+
rescue_from ActiveJob::DeserializationError do |exception|
20+
logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{exception.cause.inspect}."
2121
end
2222
end
2323

sentry-rails/lib/sentry/rails/active_job.rb

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
module Sentry
22
module Rails
33
module ActiveJobExtensions
4-
def self.included(base)
5-
base.class_eval do
6-
around_perform do |job, block|
7-
if Sentry.initialized?
8-
if already_supported_by_specific_integration?(job)
9-
block.call
10-
else
11-
Sentry.with_scope do |scope|
12-
capture_and_reraise_with_sentry(job, scope, block)
13-
end
14-
end
15-
else
16-
block.call
4+
def perform_now
5+
if !Sentry.initialized? || already_supported_by_sentry_integration?
6+
super
7+
else
8+
Sentry.with_scope do |scope|
9+
capture_and_reraise_with_sentry(scope) do
10+
super
1711
end
1812
end
1913
end
2014
end
2115

22-
def capture_and_reraise_with_sentry(job, scope, block)
23-
scope.set_transaction_name(job.class.name)
16+
def capture_and_reraise_with_sentry(scope, &block)
17+
scope.set_transaction_name(self.class.name)
2418
transaction =
25-
if job.is_a?(::Sentry::SendEventJob)
19+
if is_a?(::Sentry::SendEventJob)
2620
nil
2721
else
2822
Sentry.start_transaction(name: scope.transaction_name, op: "active_job")
@@ -32,42 +26,40 @@ def capture_and_reraise_with_sentry(job, scope, block)
3226

3327
block.call
3428

35-
finish_transaction(transaction, 200)
29+
finish_sentry_transaction(transaction, 200)
3630
rescue Exception => e # rubocop:disable Lint/RescueException
37-
rescue_handler_result = rescue_with_handler(e)
38-
finish_transaction(transaction, 500)
39-
return rescue_handler_result if rescue_handler_result
31+
finish_sentry_transaction(transaction, 500)
4032

4133
Sentry::Rails.capture_exception(
4234
e,
43-
extra: sentry_context(job),
35+
extra: sentry_context,
4436
tags: {
45-
job_id: job.job_id,
46-
provider_job_id: job.provider_job_id
37+
job_id: job_id,
38+
provider_job_id:provider_job_id
4739
}
4840
)
4941
raise e
5042
end
5143

52-
def finish_transaction(transaction, status)
44+
def finish_sentry_transaction(transaction, status)
5345
return unless transaction
5446

5547
transaction.set_http_status(status)
5648
transaction.finish
5749
end
5850

59-
def already_supported_by_specific_integration?(job)
60-
Sentry.configuration.rails.skippable_job_adapters.include?(job.class.queue_adapter.class.to_s)
51+
def already_supported_by_sentry_integration?
52+
Sentry.configuration.rails.skippable_job_adapters.include?(self.class.queue_adapter.class.to_s)
6153
end
6254

63-
def sentry_context(job)
55+
def sentry_context
6456
{
65-
active_job: job.class.name,
66-
arguments: job.arguments,
67-
scheduled_at: job.scheduled_at,
68-
job_id: job.job_id,
69-
provider_job_id: job.provider_job_id,
70-
locale: job.locale
57+
active_job: self.class.name,
58+
arguments: arguments,
59+
scheduled_at: scheduled_at,
60+
job_id: job_id,
61+
provider_job_id: provider_job_id,
62+
locale: locale
7163
}
7264
end
7365
end

sentry-rails/spec/sentry/rails/activejob_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ class RescuedActiveJob < FailedWithExtraJob
3535
def rescue_callback(error); end
3636
end
3737

38+
class ProblematicRescuedActiveJob < FailedWithExtraJob
39+
rescue_from TestError, with: :rescue_callback
40+
41+
def rescue_callback(error)
42+
raise "foo"
43+
end
44+
end
45+
3846
RSpec.describe "without Sentry initialized" do
3947
it "runs job" do
4048
expect { FailedJob.perform_now }.to raise_error(FailedJob::TestError)
@@ -190,10 +198,29 @@ def perform(event, hint)
190198

191199
context 'using rescue_from' do
192200
it 'does not trigger Sentry' do
201+
expect_any_instance_of(RescuedActiveJob).to receive(:rescue_callback).once.and_call_original
202+
193203
expect { RescuedActiveJob.perform_now }.not_to raise_error
194204

195205
expect(transport.events.size).to eq(0)
196206
end
207+
208+
context "with exception in rescue_from" do
209+
it "reports both the original error and callback error" do
210+
expect_any_instance_of(ProblematicRescuedActiveJob).to receive(:rescue_callback).once.and_call_original
211+
212+
expect { ProblematicRescuedActiveJob.perform_now }.to raise_error(RuntimeError)
213+
214+
expect(transport.events.size).to eq(1)
215+
216+
event = transport.events.first
217+
exceptions_data = event.exception.to_hash[:values]
218+
219+
expect(exceptions_data.count).to eq(2)
220+
expect(exceptions_data[0][:type]).to eq("FailedJob::TestError")
221+
expect(exceptions_data[1][:type]).to eq("RuntimeError")
222+
end
223+
end
197224
end
198225

199226
context "when we are using an adapter which has a specific integration" do

0 commit comments

Comments
 (0)