Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tracing support to sentry-delayed_job #1482

Merged
merged 1 commit into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Add `sentry-resque` [#1476](https://github.com/getsentry/sentry-ruby/pull/1476)
- Add tracing support to `sentry-resque` [#1480](https://github.com/getsentry/sentry-ruby/pull/1480)
- Set user to the current scope via sidekiq middleware [#1469](https://github.com/getsentry/sentry-ruby/pull/1469)
- Add tracing support to `sentry-delayed_job` [#1482](https://github.com/getsentry/sentry-ruby/pull/1482)

### Bug Fixes

Expand Down
30 changes: 27 additions & 3 deletions sentry-delayed_job/lib/sentry/delayed_job/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ class Plugin < ::Delayed::Plugin
next block.call(job, *args) unless Sentry.initialized?

Sentry.with_scope do |scope|
scope.set_contexts(**generate_contexts(job))
contexts = generate_contexts(job)
scope.set_transaction_name(contexts.dig(ACTIVE_JOB_CONTEXT_KEY, :job_class) || contexts.dig(DELAYED_JOB_CONTEXT_KEY, :job_class))
scope.set_contexts(**contexts)
scope.set_tags("delayed_job.queue" => job.queue, "delayed_job.id" => job.id.to_s)

transaction = Sentry.start_transaction(name: scope.transaction_name, op: "delayed_job")
scope.set_span(transaction) if transaction

begin
block.call(job, *args)

finish_transaction(transaction, 200)
rescue Exception => e
capture_exception(e, job)

finish_transaction(transaction, 500)
raise
end
end
Expand All @@ -40,7 +47,8 @@ def self.generate_contexts(job)
queue: job.queue,
created_at: job.created_at,
last_error: job.last_error&.byteslice(0..1000),
handler: job.handler&.byteslice(0..1000)
handler: job.handler&.byteslice(0..1000),
job_class: compute_job_class(job.payload_object),
}

if job.payload_object.respond_to?(:job_data)
Expand All @@ -54,6 +62,15 @@ def self.generate_contexts(job)
context
end

def self.compute_job_class(payload_object)
if payload_object.is_a? Delayed::PerformableMethod
klass = payload_object.object.is_a?(Class) ? payload_object.object.name : payload_object.object.class.name
"#{klass}##{payload_object.method_name}"
else
payload_object.class.name
end
end

def self.capture_exception(exception, job)
Sentry::DelayedJob.capture_exception(exception, hint: { background: false }) if report?(job)
end
Expand All @@ -65,6 +82,13 @@ def self.report?(job)
# count at this point.
job.attempts >= Delayed::Worker.max_attempts.pred
end

def self.finish_transaction(transaction, status)
return unless transaction

transaction.set_http_status(status)
transaction.finish
end
end
end
end
Expand Down
116 changes: 116 additions & 0 deletions sentry-delayed_job/spec/sentry/delayed_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ def tagged_report(number: 1)
def report
Sentry.capture_message("report")
end

def do_nothing
end

def self.class_do_nothing
end
end

it "sets correct extra/tags context for each job" do
Expand Down Expand Up @@ -234,6 +240,116 @@ def perform
expect(event[:contexts][:"Active-Job"][:job_class]).to eq("FailedJob")
end
end

context "when tracing is enabled" do
before do
perform_basic_setup do |config|
config.traces_sample_rate = 1.0
fsateler marked this conversation as resolved.
Show resolved Hide resolved
config.rails.skippable_job_adapters << "ActiveJob::QueueAdapters::DelayedJobAdapter"
end
end

it "records transaction" do
ReportingJob.perform_later

enqueued_job = Delayed::Backend::ActiveRecord::Job.last
enqueued_job.invoke_job

expect(transport.events.count).to eq(2)
transaction = transport.events.last

expect(transaction.transaction).to eq("ReportingJob")
expect(transaction.contexts.dig(:trace, :trace_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :span_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :status)).to eq("ok")
end

it "records transaction with exception" do
FailedJob.perform_later
enqueued_job = Delayed::Backend::ActiveRecord::Job.last
begin
enqueued_job.invoke_job
rescue ZeroDivisionError
nil
end

expect(transport.events.count).to eq(2)
transaction = transport.events.last

expect(transaction.transaction).to eq("FailedJob")
expect(transaction.contexts.dig(:trace, :trace_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :span_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :status)).to eq("internal_error")

event = transport.events.last
expect(event.contexts.dig(:trace, :trace_id)).to eq(transaction.contexts.dig(:trace, :trace_id))
end
end
end

context ".compute_job_class" do
it 'returns the class and method name for a delayed instance method call' do
Post.new.delay.do_nothing
enqueued_job = Delayed::Backend::ActiveRecord::Job.last

expect(Sentry::DelayedJob::Plugin.compute_job_class(enqueued_job.payload_object)).to eq("Post#do_nothing")
end

it 'returns the class and method name for a delayed class method call' do
Post.delay.class_do_nothing
enqueued_job = Delayed::Backend::ActiveRecord::Job.last

expect(Sentry::DelayedJob::Plugin.compute_job_class(enqueued_job.payload_object)).to eq("Post#class_do_nothing")
end

it 'returns the class name for anything else' do

expect(Sentry::DelayedJob::Plugin.compute_job_class("something")).to eq("String")
expect(Sentry::DelayedJob::Plugin.compute_job_class(Sentry::DelayedJob::Plugin)).to eq("Class")
end
end

context "when tracing is enabled" do
before do
perform_basic_setup do |config|
config.traces_sample_rate = 1.0
end
end

it "records transaction" do
Post.new.delay.do_nothing
enqueued_job = Delayed::Backend::ActiveRecord::Job.last
enqueued_job.invoke_job

expect(transport.events.count).to eq(1)
transaction = transport.events.last

expect(transaction.transaction).to eq("Post#do_nothing")
expect(transaction.contexts.dig(:trace, :trace_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :span_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :status)).to eq("ok")
end

it "records transaction with exception" do
Post.new.delay.raise_error
enqueued_job = Delayed::Backend::ActiveRecord::Job.last
begin
enqueued_job.invoke_job
rescue ZeroDivisionError
nil
end

expect(transport.events.count).to eq(2)
transaction = transport.events.last

expect(transaction.transaction).to eq("Post#raise_error")
expect(transaction.contexts.dig(:trace, :trace_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :span_id)).to be_a(String)
expect(transaction.contexts.dig(:trace, :status)).to eq("internal_error")

event = transport.events.last
expect(event.contexts.dig(:trace, :trace_id)).to eq(transaction.contexts.dig(:trace, :trace_id))
end
end
end

Expand Down
1 change: 1 addition & 0 deletions sentry-delayed_job/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ def perform_basic_setup
config.logger = ::Logger.new(nil)
config.background_worker_threads = 0
config.transport.transport_class = Sentry::DummyTransport
yield(config) if block_given?
end
end