diff --git a/sentry-rails/spec/active_job/shared_examples/adapter_skipping.rb b/sentry-rails/spec/active_job/shared_examples/adapter_skipping.rb index 35d4896b0..14c3ac81b 100644 --- a/sentry-rails/spec/active_job/shared_examples/adapter_skipping.rb +++ b/sentry-rails/spec/active_job/shared_examples/adapter_skipping.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples "an ActiveJob backend that respects skippable_job_adapters" do - it "captures no events when the adapter is in skippable_job_adapters" do + it "leaves error reporting to the backend when the adapter is in skippable_job_adapters" do Sentry.configuration.rails.skippable_job_adapters = [ failing_job.queue_adapter.class.to_s ] @@ -11,6 +11,6 @@ drain end.to raise_error(RuntimeError, /boom from failing_job spec/) - expect(sentry_events).to be_empty + expect(sentry_events.size).to eq(skipped_adapter_error_events) end end diff --git a/sentry-rails/spec/active_job/shared_examples/tracing/consumer_transaction.rb b/sentry-rails/spec/active_job/shared_examples/tracing/consumer_transaction.rb index 9d0be2ef0..155f3b439 100644 --- a/sentry-rails/spec/active_job/shared_examples/tracing/consumer_transaction.rb +++ b/sentry-rails/spec/active_job/shared_examples/tracing/consumer_transaction.rb @@ -14,7 +14,7 @@ expect(transaction.transaction).to eq(successful_job.name) expect(transaction.transaction_info).to eq(source: :task) expect(transaction.contexts.dig(:trace, :op)).to eq("queue.process") - expect(transaction.contexts.dig(:trace, :origin)).to eq("auto.queue.active_job") + expect(transaction.contexts.dig(:trace, :origin)).to eq(job_span_origin) expect(transaction.contexts.dig(:trace, :status)).to eq("ok") end diff --git a/sentry-rails/spec/active_job/shared_examples/tracing/messaging_span_data.rb b/sentry-rails/spec/active_job/shared_examples/tracing/messaging_span_data.rb index 6dfd4d9f8..1dc1e2a7d 100644 --- a/sentry-rails/spec/active_job/shared_examples/tracing/messaging_span_data.rb +++ b/sentry-rails/spec/active_job/shared_examples/tracing/messaging_span_data.rb @@ -38,7 +38,7 @@ def perform(_greeting); end drain data = consumer_transaction.contexts.dig(:trace, :data) - expect(data["messaging.message.body.size"]).to eq(JSON.generate(delivered_job_data.last).bytesize) + expect(data["messaging.message.body.size"]).to eq(expected_message_body_size(delivered_job_data.last)) end it "records messaging.message.retry.count = 0 for non-retryable jobs" do diff --git a/sentry-rails/spec/active_job/shared_examples/tracing/producer_span.rb b/sentry-rails/spec/active_job/shared_examples/tracing/producer_span.rb index 278de41a9..b9333c1ed 100644 --- a/sentry-rails/spec/active_job/shared_examples/tracing/producer_span.rb +++ b/sentry-rails/spec/active_job/shared_examples/tracing/producer_span.rb @@ -15,7 +15,7 @@ publish_span = parent.spans.find { |s| s[:op] == "queue.publish" } expect(publish_span).not_to be_nil expect(publish_span[:description]).to eq(successful_job.name) - expect(publish_span[:origin]).to eq("auto.queue.active_job") + expect(publish_span[:origin]).to eq(job_span_origin) expect(publish_span[:data]["messaging.system"]).to be_a(String).and(satisfy { |v| !v.empty? }) expect(publish_span[:data]["messaging.system"]).to eq(successful_job.queue_adapter_name) expect(publish_span[:data]["messaging.message.id"]).to be_a(String).and(satisfy { |v| !v.empty? }) diff --git a/sentry-rails/spec/active_job/shared_examples/tracing/trace_propagation.rb b/sentry-rails/spec/active_job/shared_examples/tracing/trace_propagation.rb index a19edb048..40602a1d2 100644 --- a/sentry-rails/spec/active_job/shared_examples/tracing/trace_propagation.rb +++ b/sentry-rails/spec/active_job/shared_examples/tracing/trace_propagation.rb @@ -121,7 +121,7 @@ def perform publish_span = parent.spans.find { |s| s[:op] == "queue.publish" } expect(publish_span).not_to be_nil expect(publish_span[:description]).to eq(successful_job.name) - expect(publish_span[:origin]).to eq("auto.queue.active_job") + expect(publish_span[:origin]).to eq(job_span_origin) end end diff --git a/sentry-rails/spec/active_job/support/harness.rb b/sentry-rails/spec/active_job/support/harness.rb index 74aee6c2d..403bfa0cb 100644 --- a/sentry-rails/spec/active_job/support/harness.rb +++ b/sentry-rails/spec/active_job/support/harness.rb @@ -17,6 +17,8 @@ let(:adapter) { adapter } let(:configure_sentry) { proc { } } let(:sentry_test_config) { nil } + let(:job_span_origin) { "auto.queue.active_job" } + let(:skipped_adapter_error_events) { 0 } # Canonical job fixtures shared by every backend's example suite. Specs # that need a custom body (e.g. local variables in the failing frame) @@ -147,6 +149,10 @@ def last_enqueued_payload "Include the matching adapter shared context (e.g. 'test adapter', 'sidekiq adapter')." end + def expected_message_body_size(delivered_job_data) + JSON.generate(delivered_job_data).bytesize + end + def job_fixture(name = nil, &block) name ||= "JobFixture_#{SecureRandom.hex(4)}" klass = Class.new(::ActiveJob::Base, &block) diff --git a/sentry-resque/Gemfile b/sentry-resque/Gemfile index b099a260b..1e7697d36 100644 --- a/sentry-resque/Gemfile +++ b/sentry-resque/Gemfile @@ -14,3 +14,12 @@ gem "resque-retry", "~> 1.8" gem "sentry-rails", path: "../sentry-rails" gem "rails", "> 5.2.0" + +unless RUBY_PLATFORM.include?("java") + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2") + gem "resque-scheduler", "~> 5.0" + gem "mock_redis" + gem "sqlite3" + gem "rspec-rails" + end +end diff --git a/sentry-resque/README.md b/sentry-resque/README.md index 687ed3128..0612a224f 100644 --- a/sentry-resque/README.md +++ b/sentry-resque/README.md @@ -35,4 +35,22 @@ gem "sentry-ruby" gem "sentry-resque" ``` -Then you're all set! `sentry-resque` will automatically insert a custom middleware and error handler to capture exceptions from your workers! +Then you're all set! `sentry-resque` captures exceptions and starts a `queue.resque` transaction for every job your Resque workers perform. + +## Maintenance Mode + +`sentry-resque` is in maintenance mode. The integration for plain Resque jobs keeps receiving fixes, but new background job features land in `sentry-rails` through its ActiveJob integration. + +### ActiveJob on Resque + +By default, `ActiveJob::QueueAdapters::ResqueAdapter` is listed in `config.rails.skippable_job_adapters`, so ActiveJob jobs running on Resque are reported by `sentry-resque`. Rails apps can opt in to the `sentry-rails` ActiveJob integration instead by removing the adapter from that list: + +```ruby +# config/initializers/sentry.rb +Sentry.init do |config| + # ... + config.rails.skippable_job_adapters.delete("ActiveJob::QueueAdapters::ResqueAdapter") +end +``` + +Once opted in, `sentry-rails` reports those jobs with Resque data: the `resque.queue` tag, the `Active-Job` context, and the `messaging.*` span data for the Resque queue. `sentry-resque` only reports errors raised before an ActiveJob job starts, such as a job class that can no longer be loaded. Plain Resque jobs are not affected. diff --git a/sentry-resque/Rakefile b/sentry-resque/Rakefile index 13afab191..92494d644 100644 --- a/sentry-resque/Rakefile +++ b/sentry-resque/Rakefile @@ -3,8 +3,24 @@ require "bundler/gem_tasks" require "rspec/core/rake_task" -RSpec::Core::RakeTask.new(:spec).tap do |task| +RSpec::Core::RakeTask.new(:spec) do |task| + task.pattern = "spec/sentry/**/*_spec.rb" task.rspec_opts = "--order rand" end -task default: :spec +namespace :spec do + isolated_tasks = Dir["spec/isolated/**/*_spec.rb"].sort.map do |file| + task_name = "isolated:#{file.delete_prefix("spec/isolated/").delete_suffix("_spec.rb")}" + + RSpec::Core::RakeTask.new(task_name) do |task| + task.pattern = file + task.rspec_opts = "--format progress" + end + + task_name + end + + task isolated: isolated_tasks +end + +task default: [:spec, :"spec:isolated"] diff --git a/sentry-resque/gemfiles/ruby-3.2.gemfile.lock b/sentry-resque/gemfiles/ruby-3.2.gemfile.lock index 873ec9077..e6e5f7edd 100644 --- a/sentry-resque/gemfiles/ruby-3.2.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.2.gemfile.lock @@ -150,6 +150,7 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) + mock_redis (0.48.1) mono_logger (1.1.2) multi_json (1.21.2) mustermann (3.1.1) @@ -276,6 +277,14 @@ GEM rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.7) @@ -304,6 +313,14 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sqlite3 (2.10.0-aarch64-linux-gnu) + sqlite3 (2.10.0-aarch64-linux-musl) + sqlite3 (2.10.0-arm-linux-gnu) + sqlite3 (2.10.0-arm-linux-musl) + sqlite3 (2.10.0-arm64-darwin) + sqlite3 (2.10.0-x86_64-darwin) + sqlite3 (2.10.0-x86_64-linux-gnu) + sqlite3 (2.10.0-x86_64-linux-musl) thor (1.5.0) tilt (2.9.0) timeout (0.6.1) @@ -332,11 +349,14 @@ DEPENDENCIES cgi debug! irb + mock_redis rails (> 5.2.0) rake (~> 12.0) resque-retry (~> 1.8) + resque-scheduler (~> 5.0) rexml (= 3.4.1) rspec + rspec-rails rspec-retry ruby-lsp-rspec sentry-rails! @@ -344,6 +364,7 @@ DEPENDENCIES sentry-ruby! simplecov simplecov-cobertura (~> 3.0) + sqlite3 CHECKSUMS action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 @@ -387,6 +408,7 @@ CHECKSUMS marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 + mock_redis (0.48.1) sha256=0f3734e83a4534740b2bdd97c727fb81e7587bb05932a6b114fb69c232b217b3 mono_logger (1.1.2) sha256=2e359def7007f5c908aadd953687991fe667995d14ae5f0d10dda76e3e8670f7 multi_json (1.21.2) sha256=245531eaf54bdba57aba515ff08b4503505c5e09911daa89c5a561e2f85bea8e mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198 @@ -432,6 +454,7 @@ CHECKSUMS rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c ruby-lsp (0.26.11) sha256=4cc8f1587237ff8c9031680b5491df901a82559eeaa1ae96863ffe700b32583d @@ -446,6 +469,14 @@ CHECKSUMS simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 + sqlite3 (2.10.0-aarch64-linux-gnu) sha256=dafeea94182af2eb53a10797be359fa6ec4270532bd45255d1c2beb69f2eb743 + sqlite3 (2.10.0-aarch64-linux-musl) sha256=ec4ffedded0bd8579629e14d0647cfee9092c0af0009c05d6404d90857b1cb8b + sqlite3 (2.10.0-arm-linux-gnu) sha256=bcf2e165f27fd28f46ba34eeba3503d557ea7f76f9dd40434ce719f2ba0d5cda + sqlite3 (2.10.0-arm-linux-musl) sha256=338510e64876c7d02d91733eeaee384806c58b3835d8a688d1bfdbf7e468c497 + sqlite3 (2.10.0-arm64-darwin) sha256=f619f33adfd01a1827e8764a3da92b289c1a99d93707d021a242dccba6909c4e + sqlite3 (2.10.0-x86_64-darwin) sha256=67306588548e62095855a511285daf33389d7f4311c1c33a1addd23e2b90cec5 + sqlite3 (2.10.0-x86_64-linux-gnu) sha256=295f48b240ba663efa2e3b6f38cbc0f19f5c8a2823a71fda3bf15531be5dd0e8 + sqlite3 (2.10.0-x86_64-linux-musl) sha256=876dd86a15c7828075ff7612e3d0ab0eb2eba0d8485b13fd4000e8a335f86bc5 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb diff --git a/sentry-resque/gemfiles/ruby-3.3.gemfile.lock b/sentry-resque/gemfiles/ruby-3.3.gemfile.lock index c6f6c81e3..be607e980 100644 --- a/sentry-resque/gemfiles/ruby-3.3.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.3.gemfile.lock @@ -150,6 +150,7 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) + mock_redis (0.48.1) mono_logger (1.1.2) multi_json (1.21.2) mustermann (3.1.1) @@ -276,6 +277,14 @@ GEM rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.7) @@ -304,6 +313,14 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sqlite3 (2.10.0-aarch64-linux-gnu) + sqlite3 (2.10.0-aarch64-linux-musl) + sqlite3 (2.10.0-arm-linux-gnu) + sqlite3 (2.10.0-arm-linux-musl) + sqlite3 (2.10.0-arm64-darwin) + sqlite3 (2.10.0-x86_64-darwin) + sqlite3 (2.10.0-x86_64-linux-gnu) + sqlite3 (2.10.0-x86_64-linux-musl) thor (1.5.0) tilt (2.9.0) timeout (0.6.1) @@ -332,11 +349,14 @@ DEPENDENCIES cgi debug! irb + mock_redis rails (> 5.2.0) rake (~> 12.0) resque-retry (~> 1.8) + resque-scheduler (~> 5.0) rexml (= 3.4.1) rspec + rspec-rails rspec-retry ruby-lsp-rspec sentry-rails! @@ -344,6 +364,7 @@ DEPENDENCIES sentry-ruby! simplecov simplecov-cobertura (~> 3.0) + sqlite3 CHECKSUMS action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 @@ -387,6 +408,7 @@ CHECKSUMS marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 + mock_redis (0.48.1) sha256=0f3734e83a4534740b2bdd97c727fb81e7587bb05932a6b114fb69c232b217b3 mono_logger (1.1.2) sha256=2e359def7007f5c908aadd953687991fe667995d14ae5f0d10dda76e3e8670f7 multi_json (1.21.2) sha256=245531eaf54bdba57aba515ff08b4503505c5e09911daa89c5a561e2f85bea8e mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198 @@ -432,6 +454,7 @@ CHECKSUMS rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c ruby-lsp (0.26.11) sha256=4cc8f1587237ff8c9031680b5491df901a82559eeaa1ae96863ffe700b32583d @@ -446,6 +469,14 @@ CHECKSUMS simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 + sqlite3 (2.10.0-aarch64-linux-gnu) sha256=dafeea94182af2eb53a10797be359fa6ec4270532bd45255d1c2beb69f2eb743 + sqlite3 (2.10.0-aarch64-linux-musl) sha256=ec4ffedded0bd8579629e14d0647cfee9092c0af0009c05d6404d90857b1cb8b + sqlite3 (2.10.0-arm-linux-gnu) sha256=bcf2e165f27fd28f46ba34eeba3503d557ea7f76f9dd40434ce719f2ba0d5cda + sqlite3 (2.10.0-arm-linux-musl) sha256=338510e64876c7d02d91733eeaee384806c58b3835d8a688d1bfdbf7e468c497 + sqlite3 (2.10.0-arm64-darwin) sha256=f619f33adfd01a1827e8764a3da92b289c1a99d93707d021a242dccba6909c4e + sqlite3 (2.10.0-x86_64-darwin) sha256=67306588548e62095855a511285daf33389d7f4311c1c33a1addd23e2b90cec5 + sqlite3 (2.10.0-x86_64-linux-gnu) sha256=295f48b240ba663efa2e3b6f38cbc0f19f5c8a2823a71fda3bf15531be5dd0e8 + sqlite3 (2.10.0-x86_64-linux-musl) sha256=876dd86a15c7828075ff7612e3d0ab0eb2eba0d8485b13fd4000e8a335f86bc5 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb diff --git a/sentry-resque/gemfiles/ruby-3.4.gemfile.lock b/sentry-resque/gemfiles/ruby-3.4.gemfile.lock index d40c6efab..589620820 100644 --- a/sentry-resque/gemfiles/ruby-3.4.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.4.gemfile.lock @@ -151,6 +151,7 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) + mock_redis (0.48.1) mono_logger (1.1.2) multi_json (1.21.2) mustermann (3.1.1) @@ -279,6 +280,14 @@ GEM rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.7) @@ -307,6 +316,14 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sqlite3 (2.10.0-aarch64-linux-gnu) + sqlite3 (2.10.0-aarch64-linux-musl) + sqlite3 (2.10.0-arm-linux-gnu) + sqlite3 (2.10.0-arm-linux-musl) + sqlite3 (2.10.0-arm64-darwin) + sqlite3 (2.10.0-x86_64-darwin) + sqlite3 (2.10.0-x86_64-linux-gnu) + sqlite3 (2.10.0-x86_64-linux-musl) thor (1.5.0) tilt (2.9.0) timeout (0.6.1) @@ -338,13 +355,16 @@ DEPENDENCIES debug! drb irb + mock_redis mutex_m ostruct rails (> 5.2.0) rake (~> 12.0) resque-retry (~> 1.8) + resque-scheduler (~> 5.0) rexml (= 3.4.1) rspec + rspec-rails rspec-retry ruby-lsp-rspec sentry-rails! @@ -352,6 +372,7 @@ DEPENDENCIES sentry-ruby! simplecov simplecov-cobertura (~> 3.0) + sqlite3 CHECKSUMS action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 @@ -396,6 +417,7 @@ CHECKSUMS marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 + mock_redis (0.48.1) sha256=0f3734e83a4534740b2bdd97c727fb81e7587bb05932a6b114fb69c232b217b3 mono_logger (1.1.2) sha256=2e359def7007f5c908aadd953687991fe667995d14ae5f0d10dda76e3e8670f7 multi_json (1.21.2) sha256=245531eaf54bdba57aba515ff08b4503505c5e09911daa89c5a561e2f85bea8e mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198 @@ -443,6 +465,7 @@ CHECKSUMS rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c ruby-lsp (0.26.11) sha256=4cc8f1587237ff8c9031680b5491df901a82559eeaa1ae96863ffe700b32583d @@ -457,6 +480,14 @@ CHECKSUMS simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 + sqlite3 (2.10.0-aarch64-linux-gnu) sha256=dafeea94182af2eb53a10797be359fa6ec4270532bd45255d1c2beb69f2eb743 + sqlite3 (2.10.0-aarch64-linux-musl) sha256=ec4ffedded0bd8579629e14d0647cfee9092c0af0009c05d6404d90857b1cb8b + sqlite3 (2.10.0-arm-linux-gnu) sha256=bcf2e165f27fd28f46ba34eeba3503d557ea7f76f9dd40434ce719f2ba0d5cda + sqlite3 (2.10.0-arm-linux-musl) sha256=338510e64876c7d02d91733eeaee384806c58b3835d8a688d1bfdbf7e468c497 + sqlite3 (2.10.0-arm64-darwin) sha256=f619f33adfd01a1827e8764a3da92b289c1a99d93707d021a242dccba6909c4e + sqlite3 (2.10.0-x86_64-darwin) sha256=67306588548e62095855a511285daf33389d7f4311c1c33a1addd23e2b90cec5 + sqlite3 (2.10.0-x86_64-linux-gnu) sha256=295f48b240ba663efa2e3b6f38cbc0f19f5c8a2823a71fda3bf15531be5dd0e8 + sqlite3 (2.10.0-x86_64-linux-musl) sha256=876dd86a15c7828075ff7612e3d0ab0eb2eba0d8485b13fd4000e8a335f86bc5 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb diff --git a/sentry-resque/gemfiles/ruby-4.0.gemfile.lock b/sentry-resque/gemfiles/ruby-4.0.gemfile.lock index d40c6efab..589620820 100644 --- a/sentry-resque/gemfiles/ruby-4.0.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-4.0.gemfile.lock @@ -151,6 +151,7 @@ GEM minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) + mock_redis (0.48.1) mono_logger (1.1.2) multi_json (1.21.2) mustermann (3.1.1) @@ -279,6 +280,14 @@ GEM rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.7) @@ -307,6 +316,14 @@ GEM rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sqlite3 (2.10.0-aarch64-linux-gnu) + sqlite3 (2.10.0-aarch64-linux-musl) + sqlite3 (2.10.0-arm-linux-gnu) + sqlite3 (2.10.0-arm-linux-musl) + sqlite3 (2.10.0-arm64-darwin) + sqlite3 (2.10.0-x86_64-darwin) + sqlite3 (2.10.0-x86_64-linux-gnu) + sqlite3 (2.10.0-x86_64-linux-musl) thor (1.5.0) tilt (2.9.0) timeout (0.6.1) @@ -338,13 +355,16 @@ DEPENDENCIES debug! drb irb + mock_redis mutex_m ostruct rails (> 5.2.0) rake (~> 12.0) resque-retry (~> 1.8) + resque-scheduler (~> 5.0) rexml (= 3.4.1) rspec + rspec-rails rspec-retry ruby-lsp-rspec sentry-rails! @@ -352,6 +372,7 @@ DEPENDENCIES sentry-ruby! simplecov simplecov-cobertura (~> 3.0) + sqlite3 CHECKSUMS action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009 @@ -396,6 +417,7 @@ CHECKSUMS marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 + mock_redis (0.48.1) sha256=0f3734e83a4534740b2bdd97c727fb81e7587bb05932a6b114fb69c232b217b3 mono_logger (1.1.2) sha256=2e359def7007f5c908aadd953687991fe667995d14ae5f0d10dda76e3e8670f7 multi_json (1.21.2) sha256=245531eaf54bdba57aba515ff08b4503505c5e09911daa89c5a561e2f85bea8e mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198 @@ -443,6 +465,7 @@ CHECKSUMS rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c ruby-lsp (0.26.11) sha256=4cc8f1587237ff8c9031680b5491df901a82559eeaa1ae96863ffe700b32583d @@ -457,6 +480,14 @@ CHECKSUMS simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 + sqlite3 (2.10.0-aarch64-linux-gnu) sha256=dafeea94182af2eb53a10797be359fa6ec4270532bd45255d1c2beb69f2eb743 + sqlite3 (2.10.0-aarch64-linux-musl) sha256=ec4ffedded0bd8579629e14d0647cfee9092c0af0009c05d6404d90857b1cb8b + sqlite3 (2.10.0-arm-linux-gnu) sha256=bcf2e165f27fd28f46ba34eeba3503d557ea7f76f9dd40434ce719f2ba0d5cda + sqlite3 (2.10.0-arm-linux-musl) sha256=338510e64876c7d02d91733eeaee384806c58b3835d8a688d1bfdbf7e468c497 + sqlite3 (2.10.0-arm64-darwin) sha256=f619f33adfd01a1827e8764a3da92b289c1a99d93707d021a242dccba6909c4e + sqlite3 (2.10.0-x86_64-darwin) sha256=67306588548e62095855a511285daf33389d7f4311c1c33a1addd23e2b90cec5 + sqlite3 (2.10.0-x86_64-linux-gnu) sha256=295f48b240ba663efa2e3b6f38cbc0f19f5c8a2823a71fda3bf15531be5dd0e8 + sqlite3 (2.10.0-x86_64-linux-musl) sha256=876dd86a15c7828075ff7612e3d0ab0eb2eba0d8485b13fd4000e8a335f86bc5 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb diff --git a/sentry-resque/lib/sentry-resque.rb b/sentry-resque/lib/sentry-resque.rb index 1d499af8a..e51a1335e 100644 --- a/sentry-resque/lib/sentry-resque.rb +++ b/sentry-resque/lib/sentry-resque.rb @@ -12,15 +12,7 @@ module Resque extend Sentry::Integrable register_integration name: "resque", version: Sentry::Resque::VERSION - - if defined?(::Rails::Railtie) - class Railtie < ::Rails::Railtie - config.after_initialize do - next unless Sentry.initialized? && defined?(::Sentry::Rails) - - Sentry.configuration.rails.skippable_job_adapters << "ActiveJob::QueueAdapters::ResqueAdapter" - end - end - end end end + +require "sentry/resque/railtie" if defined?(::Rails::Railtie) diff --git a/sentry-resque/lib/sentry/resque.rb b/sentry-resque/lib/sentry/resque.rb index a2d2c811f..6286e9271 100644 --- a/sentry-resque/lib/sentry/resque.rb +++ b/sentry-resque/lib/sentry/resque.rb @@ -21,12 +21,7 @@ class << self def record(queue, worker, payload, &block) Sentry.with_scope do |scope| begin - contexts = generate_contexts(queue, worker, payload) - scope.set_contexts(**contexts) - scope.set_tags("resque.queue" => queue) - - name = contexts.dig(:"Active-Job", :job_class) || contexts.dig(:"Resque", :job_class) - scope.set_transaction_name(name, source: :task) + apply_job_scope(scope, queue, worker, payload) transaction = Sentry.start_transaction( name: scope.transaction_name, source: scope.transaction_source, @@ -87,6 +82,15 @@ def finish_transaction(transaction, status) private + def apply_job_scope(scope, queue, worker, payload) + contexts = generate_contexts(queue, worker, payload) + scope.set_contexts(**contexts) + scope.set_tags("resque.queue" => queue) + + name = contexts.dig(:"Active-Job", :job_class) || contexts.dig(:"Resque", :job_class) + scope.set_transaction_name(name, source: :task) + end + def raise_if_retries_remain(klass, exception) return unless Sentry.configuration.resque.report_after_job_retries && defined?(::Resque::Plugins::Retry) == "constant" diff --git a/sentry-resque/lib/sentry/resque/active_job_extension.rb b/sentry-resque/lib/sentry/resque/active_job_extension.rb new file mode 100644 index 000000000..accaf5b19 --- /dev/null +++ b/sentry-resque/lib/sentry/resque/active_job_extension.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "sentry/rails/active_job_extension" +require "sentry/resque/active_job_support/reporter" + +module Sentry + module Resque + # The extension sentry-rails uses for ActiveJob jobs on Resque when the app + # lets sentry-rails report them. It keeps sentry-resque's context, tags, + # origin and integration, so these jobs look the same in Sentry in either mode. + # + # @see ActiveJobSupport + class ActiveJobExtension < Sentry::Rails::ActiveJobExtension + SPAN_ORIGIN = "auto.queue.resque" + MESSAGING_SYSTEM = "resque" + + def initialize(name: :resque, integration: Sentry::Resque, origin: SPAN_ORIGIN, **dependencies) + super(name: name, integration: integration, origin: origin, **dependencies) + end + + # Adds sentry-resque's `Active-Job` context and `resque.queue` tag. + # + # @see Sentry::JobExtensions::Extension#scope_data + def scope_data(job:, **) + job_data = job._sentry_job_data || {} + context = { + job_class: job.class.name, + job_id: job.job_id, + executions: job_data["executions"], + exception_executions: job_data["exception_executions"], + locale: job_data["locale"], + enqueued_at: job_data["enqueued_at"], + queue: job.queue_name + } + context[:arguments] = job_data["arguments"] if Sentry.configuration.data_collection.queues + + { contexts: { "Active-Job": context }, tags: { "resque.queue" => job.queue_name } } + end + + # Describes the Resque message being processed. + # + # @see Sentry::JobExtensions::Extension#messaging_data + def messaging_data(job:, **) + job_data = job._sentry_job_data + { + system: MESSAGING_SYSTEM, + message_id: job.job_id, + destination: job.queue_name, + retry_count: (job.executions.to_i if job.respond_to?(:executions)), + body_size: body_size(job_data), + receive_latency: Sentry::Utils::Latency.milliseconds_since(job_data&.[]("enqueued_at")) + }.compact + end + + private + + def body_size(job_data) + ::Resque.encode("class" => ActiveJobSupport::ACTIVE_JOB_WRAPPER, "args" => [job_data]).bytesize if job_data + rescue StandardError + nil + end + end + + Sentry::Rails.register_active_job_extension(:resque, ActiveJobExtension.new) + end +end diff --git a/sentry-resque/lib/sentry/resque/active_job_support/reporter.rb b/sentry-resque/lib/sentry/resque/active_job_support/reporter.rb new file mode 100644 index 000000000..ec9d3f2a1 --- /dev/null +++ b/sentry-resque/lib/sentry/resque/active_job_support/reporter.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Sentry + module Resque + # Support for ActiveJob jobs run through Resque, which reports them in one of + # two modes: + # + # - By default, sentry-rails skips the Resque adapter and sentry-resque + # reports ActiveJob jobs like other Resque jobs. + # - When the app removes the Resque adapter from + # `config.rails.skippable_job_adapters`, sentry-rails reports them through + # {Sentry::Resque::ActiveJobExtension}, and {ActiveJobSupport::Reporter} + # keeps sentry-resque from reporting them a second time. + module ActiveJobSupport + ACTIVE_JOB_ADAPTER = "ActiveJob::QueueAdapters::ResqueAdapter" + ACTIVE_JOB_WRAPPER = "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper" + + # Returns whether the Resque payload wraps an ActiveJob job. + # + # @param payload [Hash] + # @return [Boolean] + def self.handles?(payload) + payload.is_a?(Hash) && payload["class"] == ACTIVE_JOB_WRAPPER + end + + # Prepended to {SentryReporter} when sentry-rails reports ActiveJob jobs on + # Resque. For those jobs it only captures errors sentry-rails didn't, such as + # ones raised before the job runs. + module Reporter + def record(queue, worker, payload, &block) + return super unless ActiveJobSupport.handles?(payload) + + capture_unreported_exception(queue, worker, payload, &block) + end + + private + + def capture_unreported_exception(queue, worker, payload) + yield + rescue Exception => exception # rubocop:disable Lint/RescueException + unless Sentry.exception_captured?(exception) + Sentry.with_scope do |scope| + apply_job_scope(scope, queue, worker, payload) + ::Sentry::Resque.capture_exception(exception, hint: { background: false }) + end + end + raise + end + end + end + end +end diff --git a/sentry-resque/lib/sentry/resque/railtie.rb b/sentry-resque/lib/sentry/resque/railtie.rb new file mode 100644 index 000000000..8ced9704d --- /dev/null +++ b/sentry-resque/lib/sentry/resque/railtie.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "sentry/resque/active_job_support/reporter" + +module Sentry + module Resque + # Sets up how ActiveJob jobs on Resque are reported in Rails apps. + # + # @see ActiveJobSupport + class Railtie < ::Rails::Railtie + initializer "sentry.resque.skippable_job_adapter", before: :load_environment_config do + next unless defined?(::Sentry::Rails) + + Sentry::Configuration.after(:initialize) { rails.skippable_job_adapters << ActiveJobSupport::ACTIVE_JOB_ADAPTER } + Sentry.configuration.rails.skippable_job_adapters << ActiveJobSupport::ACTIVE_JOB_ADAPTER if Sentry.initialized? + end + + config.after_initialize do + next unless defined?(::Sentry::Rails) && Sentry::Rails.active_job_owns_adapter?(ActiveJobSupport::ACTIVE_JOB_ADAPTER) + + require "sentry/resque/active_job_extension" + SentryReporter.singleton_class.prepend(ActiveJobSupport::Reporter) + end + end + end +end diff --git a/sentry-resque/sentry-resque.gemspec b/sentry-resque/sentry-resque.gemspec index 38a8d457f..9ab7fe3f9 100644 --- a/sentry-resque/sentry-resque.gemspec +++ b/sentry-resque/sentry-resque.gemspec @@ -6,7 +6,8 @@ Gem::Specification.new do |spec| spec.name = "sentry-resque" spec.version = Sentry::Resque::VERSION spec.authors = ["Sentry Team"] - spec.description = spec.summary = "A gem that provides Resque integration for the Sentry error logger" + spec.summary = "A gem that provides Resque integration for the Sentry error logger (maintenance mode)" + spec.description = "Maintenance-mode Resque integration for the Sentry error logger. ActiveJob users should use the ActiveJob integration in sentry-rails, which sentry-resque enriches with Resque data." spec.email = "accounts@sentry.io" spec.license = 'MIT' diff --git a/sentry-resque/spec/isolated/active_job_spec.rb b/sentry-resque/spec/isolated/active_job_spec.rb new file mode 100644 index 000000000..6a8ad7472 --- /dev/null +++ b/sentry-resque/spec/isolated/active_job_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "rails" +require "securerandom" + +return if Rails.version.to_f < 7.2 + +begin + require "mock_redis" + require "resque" + require "resque-scheduler" +rescue LoadError + return +end + +require_relative "support/rails_app" + +RSpec.describe "ActiveJob jobs on Resque with sentry-resque loaded", type: :job do + include ActiveSupport::Testing::TimeHelpers + include_context "active_job backend harness", adapter: :resque + include_context "resque adapter" + + let(:job_span_origin) { "auto.queue.resque" } + let(:skipped_adapter_error_events) { 1 } + + def expected_message_body_size(delivered_job_data) + Resque.encode("class" => ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper.name, "args" => [delivered_job_data]).bytesize + end + + it_behaves_like "a Sentry-instrumented ActiveJob backend" + it_behaves_like "an ActiveJob backend that supports distributed tracing" + + context "with the resque adapter opted in to the ActiveJob integration" do + let(:configure_sentry) { proc { |config| config.traces_sample_rate = 1.0 } } + + def error_events + sentry_events.grep(Sentry::ErrorEvent) + end + + it "reports a failing job once, within one consumer transaction that keeps the sentry-resque identity" do + failing_job.set(queue: "events").perform_later + + expect { drain }.to raise_error(RuntimeError, /boom from failing_job spec/) + + consumer_transactions = transactions.select { |t| t.contexts.dig(:trace, :op) == "queue.process" } + expect(consumer_transactions.size).to eq(1) + expect(error_events.size).to eq(1) + + transaction = consumer_transactions.first + error_event = error_events.first + expect(transaction.contexts.dig(:trace, :origin)).to eq("auto.queue.resque") + expect(transaction.tags).to include("resque.queue" => "events") + expect(transaction.contexts[:"Active-Job"]).to include(job_class: failing_job.name, queue: "events") + expect(error_event.tags).to include("resque.queue" => "events") + expect(error_event.contexts[:"Active-Job"]).to include(job_class: failing_job.name, queue: "events") + expect(error_event.sdk[:name]).to eq("sentry.ruby.resque") + end + + it "should report an error raised before a job whose class was removed can run once through sentry-resque" do + job = successful_job.set(queue: "events").perform_later + hide_const(job.class.name) + + expect { drain }.to raise_error(NameError) + + expect(error_events.size).to eq(1) + expect(error_events.first.sdk[:name]).to eq("sentry.ruby.resque") + expect(error_events.first.tags).to include("resque.queue" => "events") + expect(error_events.first.contexts[:"Active-Job"]).to include(job_class: job.class.name, queue: "events") + end + + it "includes the job arguments in the Active-Job context only when queue data collection is on" do + [true, false].each do |collect_queues| + Sentry.configuration.data_collection.queues = collect_queues + sentry_events.clear + failing_job.set(queue: "events").perform_later("hello") + + expect { drain }.to raise_error(RuntimeError, /boom from failing_job spec/) + + context = error_events.first.contexts[:"Active-Job"] + expect(context).to include(job_class: failing_job.name, queue: "events", executions: 0, locale: "en") + if collect_queues + expect(context[:arguments]).to eq(["hello"]) + else + expect(context).not_to have_key(:arguments) + end + end + end + + it "should keep recording plain Resque jobs in a queue.resque transaction" do + plain_job = Class.new { def self.perform; end } + stub_const("PlainResqueJob", plain_job) + + Resque::Job.create(:default, PlainResqueJob) + drain + + expect(transactions.map { |event| event.contexts.dig(:trace, :op) }).to eq(["queue.resque"]) + end + + it "records the resque messaging attributes on the consumer transaction" do + job = successful_job.set(queue: "events").perform_later + drain + + expect(consumer_transaction.contexts.dig(:trace, :data)).to include( + "messaging.system" => "resque", + "messaging.message.id" => job.job_id, + "messaging.destination.name" => "events" + ) + end + + it "records messaging.system resque on the queue.publish producer span" do + within_parent_transaction { successful_job.perform_later } + + parent = transactions.find { |t| t.contexts.dig(:trace, :op) == "test" } + publish_span = parent.spans.find { |span| span[:op] == "queue.publish" } + expect(publish_span[:data]).to include("messaging.system" => "resque") + end + end +end diff --git a/sentry-resque/spec/isolated/support/rails_app.rb b/sentry-resque/spec/isolated/support/rails_app.rb new file mode 100644 index 000000000..8e6c71153 --- /dev/null +++ b/sentry-resque/spec/isolated/support/rails_app.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +begin + require "simplecov" + SimpleCov.command_name "ResqueActiveJob" +rescue LoadError +end + +ENV["RAILS_ENV"] = "test" + +SENTRY_RAILS_SPEC_DIR = File.expand_path("../../../../sentry-rails/spec", __dir__) +$LOAD_PATH.unshift(SENTRY_RAILS_SPEC_DIR) unless $LOAD_PATH.include?(SENTRY_RAILS_SPEC_DIR) + +require "sentry-rails" +require "sentry-resque" + +require_relative "../../../../sentry-rails/spec/support/test_helper" +require_relative "../../../../sentry-rails/spec/support/test_jobs" +require "rspec/rails" +Dir["#{SENTRY_RAILS_SPEC_DIR}/active_job/support/**/*.rb"].sort.each { |file| require file } +Dir["#{SENTRY_RAILS_SPEC_DIR}/active_job/shared_examples/**/*.rb"].sort.each { |file| require file } + +module ResqueTestRailsApp + def configure + super + config.eager_load = false + config.active_job.queue_adapter = :resque + end +end + +Sentry::TestRailsApp.prepend(ResqueTestRailsApp) + +RAILS_VERSION = Rails.version.to_f +RESQUE_ACTIVE_JOB_ADAPTER = "ActiveJob::QueueAdapters::ResqueAdapter" + +Sentry::Configuration.after(:configured) { rails.skippable_job_adapters.delete(RESQUE_ACTIVE_JOB_ADAPTER) } + +RSpec.configure do |config| + config.disable_monkey_patching! + config.expect_with(:rspec) { |c| c.syntax = :expect } + + config.include(Sentry::Rails::TestHelper) + config.include(ActiveJob::TestHelper, type: :job) + + config.before(:suite) do + Sentry::TestRailsApp.load_test_schema + end + + config.after(:each) do + Sentry::Rails::Tracing.unsubscribe_tracing_events + Sentry::Rails::Tracing.remove_active_support_notifications_patch + + if Sentry.initialized? + Sentry::TestHelper.clear_sentry_events + Sentry::Rails::StructuredLogging.detach(Sentry.configuration.rails.structured_logging) + end + + Sentry::Rails::ActiveJobExtensions::SentryReporter.detach_event_handlers + + reset_sentry_globals! + + Rails.application&.cleanup! + end +end diff --git a/sentry-resque/spec/sentry/resque_spec.rb b/sentry-resque/spec/sentry/resque_spec.rb index 63b4dd354..2a0d587de 100644 --- a/sentry-resque/spec/sentry/resque_spec.rb +++ b/sentry-resque/spec/sentry/resque_spec.rb @@ -315,6 +315,46 @@ def perform expect(event[:contexts][:"Active-Job"][:job_class]).to eq("AJFailedJob") end end + + context "with tracing enabled" do + let(:transactions) { transport.events.select { |event| event.is_a?(Sentry::TransactionEvent) } } + let(:errors) { transport.events.reject { |event| event.is_a?(Sentry::TransactionEvent) } } + + it "should record a plain Resque job in a queue.resque transaction" do + perform_basic_setup do |config| + config.traces_sample_rate = 1.0 + config.rails.skippable_job_adapters = ["ActiveJob::QueueAdapters::ResqueAdapter"] + end + Resque::Job.create(:default, FailedJob) + + process_job(worker) + + expect(transactions.count).to eq(1) + expect(transactions.first.to_h.dig(:contexts, :trace)).to include(op: "queue.resque", origin: "auto.queue.resque") + expect(errors.count).to eq(1) + expect(errors.first.to_h.dig(:contexts, :Resque)).to include(job_class: "FailedJob", queue: "default", worker: worker.to_s) + end + + it "should keep the Resque transaction and error event of an ActiveJob job when the adapter is skippable" do + perform_basic_setup do |config| + config.traces_sample_rate = 1.0 + config.rails.skippable_job_adapters = ["ActiveJob::QueueAdapters::ResqueAdapter"] + end + AJFailedJob.perform_later + + process_job(worker) + + expect(transactions.count).to eq(1) + transaction = transactions.first.to_h + expect(transaction[:transaction]).to eq("AJFailedJob") + expect(transaction.dig(:contexts, :trace)).to include(op: "queue.resque", origin: "auto.queue.resque", status: "internal_error") + expect(errors.count).to eq(1) + error = errors.first.to_h + expect(error[:sdk][:name]).to eq("sentry.ruby.resque") + expect(error[:tags]).to eq("resque.queue" => "default", number: 2) + expect(error[:contexts][:"Active-Job"]).to include(job_class: "AJFailedJob", queue: "default", worker: worker.to_s) + end + end end if rails_gems end