diff --git a/README.md b/README.md index 6c0a258..5e76421 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,10 @@ class FailingWorker end ``` +##### Sidekiq Retries + +By default, Raygun4Ruby will unwrap `Sidekiq::JobRetry::Handled` exceptions and report the original error via `Exception#cause`. If you would prefer not to hear about retries, you can set `config.track_retried_sidekiq_jobs` to `false` in your Raygun configuration. + ### Other Configuration options For a complete list of configuration options see the [configuration.rb](https://github.com/MindscapeHQ/raygun4ruby/blob/master/lib/raygun/configuration.rb) file diff --git a/lib/raygun/configuration.rb b/lib/raygun/configuration.rb index 62b4c28..ddcc873 100644 --- a/lib/raygun/configuration.rb +++ b/lib/raygun/configuration.rb @@ -89,6 +89,9 @@ def self.proc_config_option(name) # Should we register an error handler with [Rails' built in API](https://edgeguides.rubyonrails.org/error_reporting.html) config_option :register_rails_error_handler + # Should we track jobs that are retried in Sidekiq (ones that raise Sidekiq::JobRetry::Handled). Set to "false" to ignore. + config_option :track_retried_sidekiq_jobs + # Exception classes to ignore by default IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound', 'ActionController::RoutingError', @@ -143,7 +146,8 @@ def initialize breadcrumb_level: :info, record_raw_data: false, send_in_background: false, - error_report_send_timeout: 10 + error_report_send_timeout: 10, + track_retried_sidekiq_jobs: true ) end diff --git a/lib/raygun/error_subscriber.rb b/lib/raygun/error_subscriber.rb index be7cd13..5e2eee8 100644 --- a/lib/raygun/error_subscriber.rb +++ b/lib/raygun/error_subscriber.rb @@ -16,6 +16,10 @@ def report(error, handled:, severity:, context:, source: nil) tags: ["rails_error_reporter", *tags].compact } - Raygun.track_exception(error, data) + if source == "job.sidekiq" && defined?(Sidekiq) + Raygun::SidekiqReporter.call(error, data) + else + Raygun.track_exception(error, data) + end end end \ No newline at end of file diff --git a/lib/raygun/sidekiq.rb b/lib/raygun/sidekiq.rb index 271b1fe..23082ee 100644 --- a/lib/raygun/sidekiq.rb +++ b/lib/raygun/sidekiq.rb @@ -14,6 +14,16 @@ def self.call(exception, context_hash = {}, config = nil) }, tags: ['sidekiq'] } + + if exception.is_a?(Sidekiq::JobRetry::Handled) && exception.cause + if Raygun.configuration.track_retried_sidekiq_jobs + data.merge!(sidekiq_retried: true) + exception = exception.cause + else + return false + end + end + if exception.instance_variable_defined?(:@__raygun_correlation_id) && correlation_id = exception.instance_variable_get(:@__raygun_correlation_id) data.merge!(correlation_id: correlation_id) end diff --git a/test/unit/sidekiq_failure_test.rb b/test/unit/sidekiq_failure_test.rb index 9622dca..d7853f5 100644 --- a/test/unit/sidekiq_failure_test.rb +++ b/test/unit/sidekiq_failure_test.rb @@ -1,6 +1,7 @@ require_relative "../test_helper.rb" require "sidekiq" + # Convince Sidekiq it's on a server :) module Sidekiq class << self @@ -15,6 +16,8 @@ def server? class SidekiqFailureTest < Raygun::UnitTest def setup + require "sidekiq/job_retry" + super Raygun.configuration.send_in_background = false @@ -32,6 +35,50 @@ def test_failure_backend_appears_to_work assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}" end + def test_failure_backend_unwraps_retries + WebMock.reset! + + unwrapped_stub = stub_request(:post, 'https://api.raygun.com/entries'). + with(body: /StandardError/). + to_return(status: 202) + + begin + raise StandardError.new("Some job in Sidekiq failed, oh dear!") + rescue + raise Sidekiq::JobRetry::Handled + end + + rescue Sidekiq::JobRetry::Handled => e + + response = Raygun::SidekiqReporter.call( + e, + { sidekick_name: "robin" }, + {} # config + ) + + assert_requested unwrapped_stub + assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}" + end + + def test_failured_backend_ignores_retries_if_configured + Raygun.configuration.track_retried_sidekiq_jobs = false + + begin + raise StandardError.new("Some job in Sidekiq failed, oh dear!") + rescue + raise Sidekiq::JobRetry::Handled + end + + rescue Sidekiq::JobRetry::Handled => e + + refute Raygun::SidekiqReporter.call(e, + { sidekick_name: "robin" }, + {} # config + ) + ensure + Raygun.configuration.track_retried_sidekiq_jobs = true + end + # See https://github.com/MindscapeHQ/raygun4ruby/issues/183 # (This is how Sidekiq pre 7.1.5 calls error handlers: https://github.com/sidekiq/sidekiq/blob/1ba89bbb22d2fd574b11702d8b6ed63ae59e2256/lib/sidekiq/config.rb#L269) def test_failure_backend_appears_to_work_without_config_argument @@ -50,4 +97,26 @@ def test_we_are_in_sidekiqs_list_of_error_handlers assert error_handlers.include?(Raygun::SidekiqReporter) end + def test_rails_error_reporter_uses_sidekiq_reporter + WebMock.reset! + + tagged_request = stub_request(:post, 'https://api.raygun.com/entries'). + with(body: /"sidekiq"/). # should have a sidekiq tag! + to_return(status: 202) + + error = StandardError.new("Oh no! Your Sidekiq has failed!") + + response = Raygun::ErrorSubscriber.new.report( + error, + handled: true, + severity: "error", + context: { sidekick_name: "robin" }, + source: "job.sidekiq" + ) + + assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}" + + assert_requested tagged_request + end + end