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/retry_semantics.rb b/sentry-rails/spec/active_job/shared_examples/retry_semantics.rb index 4feead2f8..e82fa7d74 100644 --- a/sentry-rails/spec/active_job/shared_examples/retry_semantics.rb +++ b/sentry-rails/spec/active_job/shared_examples/retry_semantics.rb @@ -28,13 +28,13 @@ def perform Sentry.configuration.rails.active_job_report_on_retry_error = true end - it "captures one error event per attempt", skip: RAILS_VERSION < 6.0 do + it "captures one error event per attempt unless the backend reports only exhausted retries", skip: RAILS_VERSION < 6.0 do expect do retryable_job.perform_later 3.times { drain } end.to raise_error(RuntimeError, /boom from retryable_job spec/) - expect(sentry_events.size).to eq(3) + expect(sentry_events.size).to eq(reports_retried_exceptions ? 3 : 1) end 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 eadcbf240..28934bba1 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 @@ -110,7 +110,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..23c1cf22d 100644 --- a/sentry-rails/spec/active_job/support/harness.rb +++ b/sentry-rails/spec/active_job/support/harness.rb @@ -17,6 +17,9 @@ 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 } + let(:reports_retried_exceptions) { true } # 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 +150,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 f325b027d..2c192ec76 100644 --- a/sentry-resque/README.md +++ b/sentry-resque/README.md @@ -31,4 +31,24 @@ 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 is preserved and keeps receiving fixes, but new background job features land in `sentry-rails` through its ActiveJob integration. + +### ActiveJob on Resque + +By default, ActiveJob jobs running on Resque are still reported by `sentry-resque`, exactly as before. Rails apps can opt in to the `sentry-rails` ActiveJob integration instead: + +```ruby +# config/initializers/sentry.rb +Sentry.init do |config| + # ... + config.resque.active_job_integration = :rails +end +``` + +Once opted in, `sentry-rails` reports those jobs and `sentry-resque` enriches them with Resque data: the `resque.queue` tag, the `Active-Job` context, and the `messaging.*` span data for the Resque queue. `sentry-resque` still reports errors raised before the job starts, such as a job class that can no longer be loaded. If `ActiveJob::QueueAdapters::ResqueAdapter` is listed in `config.rails.skippable_job_adapters`, `sentry-resque` keeps reporting those jobs itself. Plain Resque jobs are not affected by this setting. + +Errors from ActiveJob jobs on Resque that `retry_on` retries are not reported, regardless of `config.rails.active_job_report_on_retry_error`. 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 5ab5dce17..2922385be 100644 --- a/sentry-resque/gemfiles/ruby-3.2.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.2.gemfile.lock @@ -133,7 +133,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (3.0.2) + json (3.1.0) language_server-protocol (3.17.0.6) logger (1.7.0) loofah (2.25.2) @@ -150,10 +150,11 @@ 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) - net-imap (0.6.7) + net-imap (0.6.8) date net-protocol net-pop (0.1.2) @@ -245,7 +246,7 @@ GEM redis (>= 4) reline (0.7.0) io-console (~> 0.5) - resque (3.1.0) + resque (3.2.0) base64 (~> 0.1) logger mono_logger (~> 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 @@ -361,7 +382,7 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f - bundler (4.0.16) sha256=d6ca5dd440c24f9abce9844cf44cc8e18c6a553de65a47efb4544137af92c47d + bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b cgi (0.5.2) sha256=61ca30298171190fd4fa0d8018e57ada456eae9b7a2b78526debf7f0a0e6f8bb concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a @@ -379,7 +400,7 @@ CHECKSUMS i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 - json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df + json (3.1.0) sha256=3ce7ccf8fde58960177a3a756c31f292a4c1e1dedee884547096a5b0604e3bd8 language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918 @@ -387,10 +408,11 @@ 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 - net-imap (0.6.7) sha256=b5c9573be975d856de252ee851871724da66aa2d449b2482d5690bd12bc23660 + net-imap (0.6.8) sha256=eb30bb62227b64a3af6b518b04b2375113d69f68f15de59b0193b07d98acf6d9 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.4.0) sha256=f9f90c8c6f64794a45160bf422177a336db12809de3beeeaa26b88fcd2e8c853 net-smtp (0.5.2) sha256=35103cc04d6b2d94632f9928ede07483c24b70b9c0db1f231fea43909b678f3c @@ -424,7 +446,7 @@ CHECKSUMS redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d redis-namespace (1.11.0) sha256=e91a1aa2b2d888b6dea1d4ab8d39e1ae6fac3426161feb9d91dd5cca598a2239 reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d - resque (3.1.0) sha256=b3cc063308c8bc0e0a2faf132bc92c7dbfce0bacd9f09c86576f49a51f56cf8c + resque (3.2.0) sha256=0eb8bb50948f0f2847778e83fc3c23dff0b10acc0274e2398272c688fd9809b3 resque-retry (1.9.0) sha256=4c26de2951bf97206173833f50733ac967086ef09c21644d45fbdd93e74cc10c resque-scheduler (5.0.1) sha256=4466be3d37dbcac4008c435749070063575927f1a48b9a78941e28cbcfc61a49 rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca @@ -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 @@ -458,4 +489,4 @@ CHECKSUMS zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5 BUNDLED WITH - 4.0.16 + 4.0.23 diff --git a/sentry-resque/gemfiles/ruby-3.3.gemfile.lock b/sentry-resque/gemfiles/ruby-3.3.gemfile.lock index ff5bd10ba..81dcc0907 100644 --- a/sentry-resque/gemfiles/ruby-3.3.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.3.gemfile.lock @@ -133,7 +133,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (3.0.2) + json (3.1.0) language_server-protocol (3.17.0.6) logger (1.7.0) loofah (2.25.2) @@ -150,10 +150,11 @@ 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) - net-imap (0.6.7) + net-imap (0.6.8) date net-protocol net-pop (0.1.2) @@ -245,7 +246,7 @@ GEM redis (>= 4) reline (0.7.0) io-console (~> 0.5) - resque (3.1.0) + resque (3.2.0) base64 (~> 0.1) logger mono_logger (~> 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 @@ -361,7 +382,7 @@ CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b bigdecimal (4.1.3) sha256=61ebe1e5e559bdc3cc6f2c0ee7f427321fc838f59611c294356eb04d6e21cf66 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f - bundler (4.0.16) sha256=d6ca5dd440c24f9abce9844cf44cc8e18c6a553de65a47efb4544137af92c47d + bundler (4.0.23) sha256=407ba8a1d928f412847f819aa5f8b6aef7c59fb51feb517112af8a165487287b cgi (0.5.2) sha256=61ca30298171190fd4fa0d8018e57ada456eae9b7a2b78526debf7f0a0e6f8bb concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a @@ -379,7 +400,7 @@ CHECKSUMS i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 - json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df + json (3.1.0) sha256=3ce7ccf8fde58960177a3a756c31f292a4c1e1dedee884547096a5b0604e3bd8 language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918 @@ -387,10 +408,11 @@ 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 - net-imap (0.6.7) sha256=b5c9573be975d856de252ee851871724da66aa2d449b2482d5690bd12bc23660 + net-imap (0.6.8) sha256=eb30bb62227b64a3af6b518b04b2375113d69f68f15de59b0193b07d98acf6d9 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.4.0) sha256=f9f90c8c6f64794a45160bf422177a336db12809de3beeeaa26b88fcd2e8c853 net-smtp (0.5.2) sha256=35103cc04d6b2d94632f9928ede07483c24b70b9c0db1f231fea43909b678f3c @@ -424,7 +446,7 @@ CHECKSUMS redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d redis-namespace (1.11.0) sha256=e91a1aa2b2d888b6dea1d4ab8d39e1ae6fac3426161feb9d91dd5cca598a2239 reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d - resque (3.1.0) sha256=b3cc063308c8bc0e0a2faf132bc92c7dbfce0bacd9f09c86576f49a51f56cf8c + resque (3.2.0) sha256=0eb8bb50948f0f2847778e83fc3c23dff0b10acc0274e2398272c688fd9809b3 resque-retry (1.9.0) sha256=4c26de2951bf97206173833f50733ac967086ef09c21644d45fbdd93e74cc10c resque-scheduler (5.0.1) sha256=4466be3d37dbcac4008c435749070063575927f1a48b9a78941e28cbcfc61a49 rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca @@ -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 @@ -458,4 +489,4 @@ CHECKSUMS zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5 BUNDLED WITH - 4.0.16 + 4.0.23 diff --git a/sentry-resque/gemfiles/ruby-3.4.gemfile.lock b/sentry-resque/gemfiles/ruby-3.4.gemfile.lock index 6bd9b5853..6e497c5ad 100644 --- a/sentry-resque/gemfiles/ruby-3.4.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-3.4.gemfile.lock @@ -134,7 +134,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (3.0.2) + json (3.1.0) language_server-protocol (3.17.0.6) logger (1.7.0) loofah (2.25.2) @@ -151,11 +151,12 @@ 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) mutex_m (0.3.0) - net-imap (0.6.7) + net-imap (0.6.8) date net-protocol net-pop (0.1.2) @@ -248,7 +249,7 @@ GEM redis (>= 4) reline (0.7.0) io-console (~> 0.5) - resque (3.1.0) + resque (3.2.0) base64 (~> 0.1) logger mono_logger (~> 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 @@ -388,7 +409,7 @@ CHECKSUMS i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 - json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df + json (3.1.0) sha256=3ce7ccf8fde58960177a3a756c31f292a4c1e1dedee884547096a5b0604e3bd8 language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918 @@ -396,11 +417,12 @@ 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 mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 - net-imap (0.6.7) sha256=b5c9573be975d856de252ee851871724da66aa2d449b2482d5690bd12bc23660 + net-imap (0.6.8) sha256=eb30bb62227b64a3af6b518b04b2375113d69f68f15de59b0193b07d98acf6d9 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.4.0) sha256=f9f90c8c6f64794a45160bf422177a336db12809de3beeeaa26b88fcd2e8c853 net-smtp (0.5.2) sha256=35103cc04d6b2d94632f9928ede07483c24b70b9c0db1f231fea43909b678f3c @@ -435,7 +457,7 @@ CHECKSUMS redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d redis-namespace (1.11.0) sha256=e91a1aa2b2d888b6dea1d4ab8d39e1ae6fac3426161feb9d91dd5cca598a2239 reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d - resque (3.1.0) sha256=b3cc063308c8bc0e0a2faf132bc92c7dbfce0bacd9f09c86576f49a51f56cf8c + resque (3.2.0) sha256=0eb8bb50948f0f2847778e83fc3c23dff0b10acc0274e2398272c688fd9809b3 resque-retry (1.9.0) sha256=4c26de2951bf97206173833f50733ac967086ef09c21644d45fbdd93e74cc10c resque-scheduler (5.0.1) sha256=4466be3d37dbcac4008c435749070063575927f1a48b9a78941e28cbcfc61a49 rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca @@ -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 6bd9b5853..6e497c5ad 100644 --- a/sentry-resque/gemfiles/ruby-4.0.gemfile.lock +++ b/sentry-resque/gemfiles/ruby-4.0.gemfile.lock @@ -134,7 +134,7 @@ GEM prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (3.0.2) + json (3.1.0) language_server-protocol (3.17.0.6) logger (1.7.0) loofah (2.25.2) @@ -151,11 +151,12 @@ 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) mutex_m (0.3.0) - net-imap (0.6.7) + net-imap (0.6.8) date net-protocol net-pop (0.1.2) @@ -248,7 +249,7 @@ GEM redis (>= 4) reline (0.7.0) io-console (~> 0.5) - resque (3.1.0) + resque (3.2.0) base64 (~> 0.1) logger mono_logger (~> 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 @@ -388,7 +409,7 @@ CHECKSUMS i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5 io-console (0.9.4) sha256=a403304f32928ee85e56d457b4612ca43f2703b561a3258844992bd24085fbb1 irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3 - json (3.0.2) sha256=8e6d7e7b11384c21230430cef90b71f14849a34a1f4452796670f7c981bd19df + json (3.1.0) sha256=3ce7ccf8fde58960177a3a756c31f292a4c1e1dedee884547096a5b0604e3bd8 language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918 @@ -396,11 +417,12 @@ 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 mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 - net-imap (0.6.7) sha256=b5c9573be975d856de252ee851871724da66aa2d449b2482d5690bd12bc23660 + net-imap (0.6.8) sha256=eb30bb62227b64a3af6b518b04b2375113d69f68f15de59b0193b07d98acf6d9 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.4.0) sha256=f9f90c8c6f64794a45160bf422177a336db12809de3beeeaa26b88fcd2e8c853 net-smtp (0.5.2) sha256=35103cc04d6b2d94632f9928ede07483c24b70b9c0db1f231fea43909b678f3c @@ -435,7 +457,7 @@ CHECKSUMS redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d redis-namespace (1.11.0) sha256=e91a1aa2b2d888b6dea1d4ab8d39e1ae6fac3426161feb9d91dd5cca598a2239 reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d - resque (3.1.0) sha256=b3cc063308c8bc0e0a2faf132bc92c7dbfce0bacd9f09c86576f49a51f56cf8c + resque (3.2.0) sha256=0eb8bb50948f0f2847778e83fc3c23dff0b10acc0274e2398272c688fd9809b3 resque-retry (1.9.0) sha256=4c26de2951bf97206173833f50733ac967086ef09c21644d45fbdd93e74cc10c resque-scheduler (5.0.1) sha256=4466be3d37dbcac4008c435749070063575927f1a48b9a78941e28cbcfc61a49 rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca @@ -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..0daad86f2 100644 --- a/sentry-resque/lib/sentry-resque.rb +++ b/sentry-resque/lib/sentry-resque.rb @@ -5,6 +5,7 @@ require "sentry/integrable" require "sentry/resque/configuration" require "sentry/resque/version" +require "sentry/resque/job_extension" require "sentry/resque" module Sentry @@ -13,14 +14,12 @@ module Resque 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 + def self.job_extension + @job_extension ||= JobExtension.new end + + Sentry.register_job_extension(job_extension) 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..dbc8c911c 100644 --- a/sentry-resque/lib/sentry/resque.rb +++ b/sentry-resque/lib/sentry/resque.rb @@ -5,28 +5,68 @@ module Sentry module Resque def perform - if Sentry.initialized? + if !Sentry.initialized? + super + elsif Sentry::Resque.job_extension.performed_by_active_job?(payload) + perform_with_active_job_hand_off { super } + else SentryReporter.record(queue, worker, payload) do super end - else - super end end + private + + def perform_with_active_job_hand_off + start_timestamp = Sentry.utc_now.to_f + args = payload["args"] + begin + payload["args"] = Sentry::Resque.job_extension.active_job_args(queue: queue, worker: worker, payload: payload) + yield + ensure + payload["args"] = args + end + rescue Exception => exception + unless Sentry.exception_captured?(exception) + SentryReporter.capture_exception(queue, worker, payload, exception, start_timestamp: start_timestamp) + end + raise + end + class SentryReporter SPAN_ORIGIN = "auto.queue.resque" class << self + def capture_exception(queue, worker, payload, exception, start_timestamp:) + Sentry.with_scope do |scope| + apply_job_scope(scope, queue, worker, payload) + job_data = payload["args"].first + options = { + name: scope.transaction_name, + source: scope.transaction_source, + op: "queue.process", + origin: SPAN_ORIGIN, + start_timestamp: start_timestamp + } + continued = Sentry.continue_trace(job_data.dig("_sentry", "trace_propagation_headers") || {}, **options) + transaction = Sentry.start_transaction(transaction: continued, **options) + if transaction + scope.set_span(transaction) + transaction.set_data(Span::DataConventions::MESSAGING_SYSTEM, "resque") + transaction.set_data(Span::DataConventions::MESSAGING_MESSAGE_ID, job_data["job_id"]) + transaction.set_data(Span::DataConventions::MESSAGING_DESTINATION_NAME, queue) + end + + ::Sentry::Resque.capture_exception(exception, hint: { background: false }) + finish_transaction(transaction, 500) + end + end + 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 +127,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/configuration.rb b/sentry-resque/lib/sentry/resque/configuration.rb index 126d6f7eb..e1581ad73 100644 --- a/sentry-resque/lib/sentry/resque/configuration.rb +++ b/sentry-resque/lib/sentry/resque/configuration.rb @@ -15,8 +15,11 @@ class Configuration # retry if it fails. attr_accessor :report_after_job_retries + attr_accessor :active_job_integration + def initialize @report_after_job_retries = false + @active_job_integration = :resque end end end diff --git a/sentry-resque/lib/sentry/resque/job_extension.rb b/sentry-resque/lib/sentry/resque/job_extension.rb new file mode 100644 index 000000000..f72bfd538 --- /dev/null +++ b/sentry-resque/lib/sentry/resque/job_extension.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module Sentry + module Resque + class JobExtension < Sentry::JobExtensions::Extension + ACTIVE_JOB_ADAPTER = "ActiveJob::QueueAdapters::ResqueAdapter" + ACTIVE_JOB_WRAPPER = "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper" + SPAN_ORIGIN = "auto.queue.resque" + RESQUE_JOB_KEY = "_sentry_resque_job" + + def initialize + super(name: "resque") + end + + def handles?(job) + handles_active_job?(job) + end + + def performed_by_active_job?(payload) + payload["class"] == ACTIVE_JOB_WRAPPER && + active_job_handoff? + end + + def active_job_args(queue:, worker:, payload:) + active_job_payload, *arguments = payload["args"] + return payload["args"] unless active_job_payload.is_a?(Hash) + + resque_job = { "queue" => queue, "worker" => worker.to_s, "payload" => payload.dup } + [active_job_payload.merge(RESQUE_JOB_KEY => resque_job), *arguments] + end + + def scope_data(job:) + return {} unless handles_active_job?(job) + + job_data = active_job_data(job) || {} + resque_job = handed_over_resque_job(job_data) + queue = resque_job.fetch("queue", job.queue_name) + 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: queue + } + context[:arguments] = job_data["arguments"] if Sentry.configuration.data_collection.queues + context[:worker] = resque_job["worker"] if resque_job.key?("worker") + + { contexts: { "Active-Job": context }, tags: { "resque.queue" => queue } } + end + + def messaging_data(job:) + return {} unless handles_active_job?(job) + + job_data = active_job_data(job) + resque_job = handed_over_resque_job(job_data) + { + system: name, + message_id: job.job_id, + destination: resque_job.fetch("queue", job.queue_name), + body_size: body_size(resque_job.fetch("payload") { wrapper_payload(job_data) }), + receive_latency: Sentry::Utils::Latency.milliseconds_since(job_data&.[]("enqueued_at")) + }.compact + end + + def transaction_name(job:) + job.class.name if handles_active_job?(job) + end + + def span_origin(job:) + SPAN_ORIGIN if handles_active_job?(job) + end + + def integration(job:) + Sentry::Resque if handles_active_job?(job) + end + + def report_retried_exception?(job:, exception:) + false if handles_active_job?(job) + end + + private + + def handles_active_job?(job) + return false unless defined?(::ActiveJob::Base) && job.is_a?(::ActiveJob::Base) + + job.class.queue_adapter.class.name == ACTIVE_JOB_ADAPTER && active_job_handoff? + end + + def active_job_handoff? + active_job_owns_adapter? && Sentry.configuration.resque.active_job_integration == :rails + end + + def active_job_owns_adapter? + return false unless defined?(::Sentry::Rails::ActiveJobExtensions) + return false unless ::Sentry::Rails.respond_to?(:active_job_owns_adapter?) + + ::Sentry::Rails.active_job_owns_adapter?(ACTIVE_JOB_ADAPTER) + end + + def active_job_data(job) + job._sentry_job_data if job.respond_to?(:_sentry_job_data) + end + + def handed_over_resque_job(job_data) + resque_job = job_data[RESQUE_JOB_KEY] if job_data.is_a?(Hash) + resque_job.is_a?(Hash) ? resque_job : {} + end + + def wrapper_payload(job_data) + { "class" => ACTIVE_JOB_WRAPPER, "args" => [job_data] } if job_data + end + + def body_size(payload) + ::Resque.encode(payload).bytesize if payload + rescue StandardError + nil + 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..245043b62 --- /dev/null +++ b/sentry-resque/lib/sentry/resque/railtie.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Sentry + module Resque + class Railtie < ::Rails::Railtie + config.after_initialize do + next unless Sentry.initialized? && defined?(::Sentry::Rails) + next if Sentry.configuration.resque.active_job_integration == :rails + + Sentry.configuration.rails.skippable_job_adapters << JobExtension::ACTIVE_JOB_ADAPTER + end + end + end +end diff --git a/sentry-resque/sentry-resque.gemspec b/sentry-resque/sentry-resque.gemspec index 749755d38..11d95e1c4 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..3ec7bb32f --- /dev/null +++ b/sentry-resque/spec/isolated/active_job_spec.rb @@ -0,0 +1,160 @@ +# 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 } + let(:reports_retried_exceptions) { false } + + def expected_message_body_size(delivered_job_data) + active_job_payload = delivered_job_data.except(Sentry::Resque::JobExtension::RESQUE_JOB_KEY) + Resque.encode("class" => ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper.name, "args" => [active_job_payload]).bytesize + end + + it_behaves_like "a Sentry-instrumented ActiveJob backend" + it_behaves_like "an ActiveJob backend that supports distributed tracing" + + context "after Sentry.init runs again without the ActiveJob hand-off" do + before do + Sentry.init do |config| + config.dsn = Sentry::TestHelper::DUMMY_DSN + config.transport.transport_class = Sentry::DummyTransport + config.background_worker_threads = 0 + config.sdk_logger = ::Logger.new(nil) + config.traces_sample_rate = 1.0 + end + end + + it "should preserve Rails identity and serialized trace propagation when the job succeeds" do + parent = Sentry.start_transaction(name: "enqueue", op: "test") + Sentry.get_current_scope.set_span(parent) + job = successful_job.perform_later + payload = last_enqueued_payload + parent.finish + Sentry.get_current_scope.clear + + drain + + consumer_transactions = transactions.select { |event| event.contexts.dig(:trace, :op).start_with?("queue.") } + rails_transaction = consumer_transactions.find { |event| event.contexts.key?(:active_job) } + + aggregate_failures do + expect(consumer_transactions.size).to eq(2) + expect(rails_transaction.transaction).to eq(job.class.name) + expect(rails_transaction.contexts.dig(:trace, :origin)).to eq("auto.queue.active_job") + expect(payload.dig("_sentry", "trace_propagation_headers", "sentry-trace")) + .to start_with("#{parent.trace_id}-") + expect(rails_transaction.contexts.dig(:trace, :trace_id)).to eq(parent.trace_id) + end + end + + it "should preserve the Rails error mechanism and transaction identity when the job fails" do + job = failing_job.perform_later + + expect { drain }.to raise_error(RuntimeError, /boom from failing_job spec/) + + rails_transaction = transactions.find { |event| event.contexts.key?(:active_job) } + error = sentry_events.grep(Sentry::ErrorEvent).find { |event| event.contexts.key?(:active_job) } + + aggregate_failures do + expect(rails_transaction.transaction).to eq(job.class.name) + expect(rails_transaction.contexts.dig(:trace, :origin)).to eq("auto.queue.active_job") + expect(error.transaction).to eq(job.class.name) + expect(error.exception.values.first.mechanism.type).to eq("rails") + expect(error.contexts.dig(:trace, :span_id)).to eq(rails_transaction.contexts.dig(:trace, :span_id)) + end + end + end + + 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 record one failed consumer transaction when a queued job class is removed" do + parent_trace_id = nil + job = nil + within_parent_transaction do |parent| + parent_trace_id = parent.trace_id + job = successful_job.set(queue: "events").perform_later + end + hide_const(job.class.name) + + expect { drain }.to raise_error(NameError) + + consumer_transactions = transactions.select { |event| event.contexts.dig(:trace, :op) == "queue.process" } + expect(error_events.size).to eq(1) + expect(consumer_transactions.size).to eq(1) + + transaction = consumer_transactions.first + expect(transaction.transaction).to eq(job.class.name) + expect(transaction.contexts.dig(:trace, :status)).to eq("internal_error") + expect(transaction.contexts.dig(:trace, :origin)).to eq("auto.queue.resque") + expect(transaction.contexts.dig(:trace, :trace_id)).to eq(parent_trace_id) + expect(transaction.contexts.dig(:trace, :data)).to include( + "messaging.system" => "resque", + "messaging.message.id" => job.job_id, + "messaging.destination.name" => "events" + ) + expect(error_events.first.contexts.dig(:trace, :span_id)).to eq(transaction.contexts.dig(:trace, :span_id)) + 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..770d5e770 --- /dev/null +++ b/sentry-resque/spec/isolated/support/rails_app.rb @@ -0,0 +1,69 @@ +# 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 = Sentry::Resque::JobExtension::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.before(:each) do + if Sentry.initialized? + Sentry.configuration.resque.active_job_integration = :rails + Sentry.configuration.rails.skippable_job_adapters -= [RESQUE_ACTIVE_JOB_ADAPTER] + end + 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/job_extension_spec.rb b/sentry-resque/spec/sentry/resque/job_extension_spec.rb new file mode 100644 index 000000000..43f46a0a3 --- /dev/null +++ b/sentry-resque/spec/sentry/resque/job_extension_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "spec_helper" +require "rails" +require "active_job" +require "sentry-rails" +require "sentry/rails/active_job" + +RSpec.describe Sentry::Resque::JobExtension do + class ResqueExtensionJob < ActiveJob::Base + self.queue_adapter = :resque + + def perform(message); end + end + + class InlineExtensionJob < ActiveJob::Base + self.queue_adapter = :inline + + def perform; end + end + + subject(:extension) { Sentry::Resque.job_extension } + + let(:enqueued_at) { (Time.now - 2).utc.iso8601(9) } + + let(:raw_resque_message) do + ResqueExtensionJob.set(queue: :default).perform_later("hello") + message = Resque.decode(Resque.redis.lpop("queue:default")) + message["args"][0]["enqueued_at"] = enqueued_at + Resque.encode(message) + end + + let(:resque_payload) { Resque.decode(raw_resque_message) } + + let(:job) { ActiveJob::Base.deserialize(resque_payload["args"].first) } + + before do + ActiveJob::Base.logger = nil + ActiveJob::Base.send(:prepend, Sentry::Rails::ActiveJobExtensions) + perform_basic_setup do |config| + config.resque.active_job_integration = :rails + end + end + + it "should be the registered extension for ActiveJob jobs on Resque" do + expect(Sentry.job_extensions.for(job)).to equal(extension) + end + + describe "#handles?" do + it "should claim ActiveJob jobs when they run on the Resque adapter" do + expect(extension.handles?(job)).to be(true) + end + + it "should not claim ActiveJob jobs when they run on another adapter" do + expect(extension.handles?(InlineExtensionJob.new)).to be(false) + end + + it "should not claim raw Resque payloads" do + expect(extension.handles?(resque_payload)).to be(false) + end + end + + it "should give the consumer the Resque origin" do + expect(extension.span_origin(job: job)).to eq("auto.queue.resque") + end + + it "should name the transaction after the ActiveJob class" do + expect(extension.transaction_name(job: job)).to eq("ResqueExtensionJob") + end + + it "should capture exceptions under the Resque integration" do + expect(extension.integration(job: job)).to eq(Sentry::Resque) + end + + describe "#scope_data" do + it "should tag the Resque queue and add the Active-Job context" do + Sentry.configuration.data_collection.queues = true + + data = extension.scope_data(job: job) + + expect(data[:tags]).to eq("resque.queue" => "default") + expect(data[:contexts][:"Active-Job"]).to include( + job_class: "ResqueExtensionJob", + job_id: job.job_id, + arguments: ["hello"], + executions: 0, + exception_executions: {}, + locale: "en", + enqueued_at: enqueued_at, + queue: "default" + ) + end + + it "should leave the job arguments out of the Active-Job context when queue data collection is off" do + Sentry.configuration.data_collection.queues = false + + context = extension.scope_data(job: job)[:contexts][:"Active-Job"] + + expect(context).not_to have_key(:arguments) + expect(context).to include(job_id: job.job_id, queue: "default") + end + end + + describe "#messaging_data" do + it "should describe the Resque message" do + data = extension.messaging_data(job: job) + + expect(data).to include( + system: "resque", + message_id: job.job_id, + destination: "default", + body_size: raw_resque_message.bytesize + ) + expect(data[:receive_latency]).to be >= 2000 + end + end +end diff --git a/sentry-resque/spec/sentry/resque_spec.rb b/sentry-resque/spec/sentry/resque_spec.rb index 63b4dd354..a7474a716 100644 --- a/sentry-resque/spec/sentry/resque_spec.rb +++ b/sentry-resque/spec/sentry/resque_spec.rb @@ -315,6 +315,178 @@ 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) } } + + { + "the Resque adapter is skippable" => [:resque, ["ActiveJob::QueueAdapters::ResqueAdapter"]], + "opted in to the sentry-rails ActiveJob integration" => [:rails, []] + }.each do |mode, (active_job_integration, skippable_job_adapters)| + it "should record a plain Resque job in a queue.resque transaction when #{mode}" do + perform_basic_setup do |config| + config.traces_sample_rate = 1.0 + config.resque.active_job_integration = active_job_integration + config.rails.skippable_job_adapters = skippable_job_adapters + 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 + 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 + + { + "by default when the adapter is not skippable" => [nil, []], + "when set to :rails but the adapter is skippable" => [:rails, ["ActiveJob::QueueAdapters::ResqueAdapter"]] + }.each do |mode, (active_job_integration, skippable_job_adapters)| + it "should keep the Resque transaction of an ActiveJob job #{mode}" do + perform_basic_setup do |config| + config.traces_sample_rate = 1.0 + config.resque.active_job_integration = active_job_integration if active_job_integration + config.rails.skippable_job_adapters = skippable_job_adapters + end + AJFailedJob.perform_later + + process_job(worker) + + resque_transaction = transactions.find { |transaction| transaction.contexts.dig(:trace, :op) == "queue.resque" } + expect(resque_transaction.to_h.dig(:contexts, :trace)).to include(origin: "auto.queue.resque", status: "internal_error") + expect(errors.count).to eq(1) + end + end + end + + context "when opted in to the sentry-rails ActiveJob integration" do + before do + perform_basic_setup do |config| + config.traces_sample_rate = 1.0 + config.resque.active_job_integration = :rails + config.rails.skippable_job_adapters = [] + end + end + + let(:transactions) { transport.events.select { |event| event.is_a?(Sentry::TransactionEvent) } } + let(:errors) { transport.events.reject { |event| event.is_a?(Sentry::TransactionEvent) } } + + it "should report a failed job once in a single queue.process transaction" do + AJFailedJob.perform_later + + expect do + process_job(worker) + end.to change { Resque::Stat.get("failed") }.by(1) + + expect(errors.count).to eq(1) + expect(errors.first.to_h[:sdk][:name]).to eq("sentry.ruby.resque") + expect(transactions.count).to eq(1) + expect(transactions.first.to_h.dig(:contexts, :trace)).to include(op: "queue.process", origin: "auto.queue.resque") + end + + it "should describe the Resque job that performed the ActiveJob job" do + AJFailedJob.perform_later + raw_resque_message = Resque.redis.lindex("queue:default", 0) + + process_job(worker) + + expect(errors.first.to_h.dig(:contexts, :"Active-Job")).to include( + job_class: "AJFailedJob", + queue: "default", + worker: worker.to_s + ) + expect(transactions.first.to_h.dig(:contexts, :trace, :data)).to include( + "messaging.message.body.size" => raw_resque_message.bytesize + ) + end + + it "should report a job whose class can no longer be loaded" do + AJMessageJob.perform_later("report from ActiveJob") + payload = Resque.decode(Resque.redis.lpop("queue:default")) + payload["args"].first["job_class"] = "RemovedJob" + Resque.redis.rpush("queue:default", Resque.encode(payload)) + + expect do + process_job(worker) + end.to change { Resque::Stat.get("failed") }.by(1) + + expect(errors.count).to eq(1) + error = errors.first.to_h + expect(error[:sdk][:name]).to eq("sentry.ruby.resque") + expect(error.dig(:exception, :values, 0, :value)).to include("RemovedJob") + expect(error[:tags]).to include("resque.queue" => "default") + expect(error[:contexts][:"Active-Job"]).to include(job_class: "RemovedJob", queue: "default", worker: worker.to_s) + end + + it "should leave the Resque payload of a failed job unchanged" do + AJFailedJob.perform_later + enqueued_payload = Resque.decode(Resque.redis.lindex("queue:default", 0)) + + process_job(worker) + + expect(Resque::Failure.all(Resque::Failure.count - 1)["payload"]).to eq(enqueued_payload) + end + + it "should report a failed job on its first run when report_after_job_retries is true" do + Sentry.configuration.resque.report_after_job_retries = true + AJFailedJob.perform_later + + process_job(worker) + + expect(errors.count).to eq(1) + end + + context "when ActiveJob retries the job" do + class AJRetriedJob < ActiveJob::Base + self.queue_adapter = :resque + retry_on ZeroDivisionError, attempts: 2, wait: ->(_executions) { nil } + + def perform + 1 / 0 + end + end + + around do |example| + Sentry::Rails::ActiveJobExtensions::SentryReporter.register_event_handlers + example.run + ensure + Sentry::Rails::ActiveJobExtensions::SentryReporter.detach_event_handlers + end + + it "should not report the retried exception even when active_job_report_on_retry_error is true" do + Sentry.configuration.rails.active_job_report_on_retry_error = true + AJRetriedJob.perform_later + + process_job(worker) + + expect(Resque.size("default")).to eq(1) + expect(errors).to be_empty + end + end + end end if rails_gems end