Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/raven/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ class Configuration
# We automatically try to set this to a git SHA or Capistrano release.
attr_accessor :release

# Array of exception classes that should not be reported until all retries
# are exhausted for a Sidekiq job.
attr_accessor :retryable_exceptions

# The sampling factor to apply to events. A value of 0.0 will not send
# any events, and a value of 1.0 will send 100% of events.
attr_accessor :sample_rate
Expand Down Expand Up @@ -198,6 +202,7 @@ def initialize
self.project_root = detect_project_root
self.rails_activesupport_breadcrumbs = false
self.rails_report_rescued_exceptions = true
self.retryable_exceptions = []
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes fail two lint rules:

  1. Assignment Branch Condition size for initialize is too high, can we just increase this from 30 to ~32? Otherwise, I'm not sure if there's a better place to put this configuration.
  2. Class has too many lines, not sure if you're OK increasing this limit as well, or if we should try pulling out some of the common functionality. For example, retryable_exception? and excluded_exception? share logic using get_exception_class, which we could pull out into a util or similar.

self.release = detect_release
self.sample_rate = 1.0
self.sanitize_credit_cards = true
Expand Down Expand Up @@ -309,6 +314,10 @@ def exception_class_allowed?(exc)
end
end

def retryable_exception?(exc)
retryable_exceptions.any? { |x| get_exception_class(x) === exc }
end

private

def detect_project_root
Expand Down
31 changes: 30 additions & 1 deletion lib/raven/integrations/sidekiq.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
require 'time'
require 'sidekiq'
begin
# Sidekiq 5 introduces JobRetry and stores the default max retry attempts there.
require 'sidekiq/job_retry'
rescue LoadError # rubocop:disable Lint/HandleExceptions
end

module Raven
class SidekiqCleanupMiddleware
Expand All @@ -15,7 +20,10 @@ def call(_worker, job, queue)
class SidekiqErrorHandler
ACTIVEJOB_RESERVED_PREFIX = "_aj_".freeze

def call(ex, context)
def call(ex, context, options = {})
configuration = options[:configuration] || Raven.configuration
return if configuration.retryable_exception?(ex) && remaining_retries?(context)

context = filter_context(context)
Raven.context.transaction.push transaction_from_context(context)
Raven.capture_exception(
Expand Down Expand Up @@ -51,6 +59,27 @@ def filter_context_hash(key, value)
[key, filter_context(value)]
end

def remaining_retries?(context)
job = context[:job] || context # Sidekiq < 4 does not have job key.
return false unless job && job["retry"]
job["retry_count"] < retry_attempts_from(job["retry"])
end

def retry_attempts_from(retries)
if retries.is_a?(Integer)
retries
else
default_max_attempts =
if defined?(Sidekiq::JobRetry)
Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS # Sidekiq 5
else
Sidekiq::Middleware::Server::RetryJobs::DEFAULT_MAX_RETRY_ATTEMPTS # Sidekiq < 5
end

Sidekiq.options.fetch(:max_retries, default_max_attempts)
end
end

# this will change in the future:
# https://github.com/mperham/sidekiq/pull/3161
def transaction_from_context(context)
Expand Down
37 changes: 37 additions & 0 deletions spec/raven/integrations/sidekiq_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

require 'raven/integrations/sidekiq'
require 'sidekiq/processor'
require 'sidekiq/job_retry'

RSpec.describe "Raven::SidekiqErrorHandler" do
let(:context) do
Expand Down Expand Up @@ -67,6 +68,42 @@

Raven::SidekiqErrorHandler.new.call(exception, aj_context)
end

context "with a retryable exception" do
class RetryableError < StandardError; end

let(:configuration) do
Raven::Configuration.new.tap do |c|
c.retryable_exceptions << RetryableError
end
end

[
{ "retry" => false, "retry_count" => 0 },
{ "retry" => true, "retry_count" => Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS },
{ "retry" => 2, "retry_count" => 2 }
].each do |retry_context|
it "captures an exception when retries are exhausted" do
exception = RetryableError.new

expect(Raven).to receive(:capture_exception).with(exception, anything)
Raven::SidekiqErrorHandler.new.call(exception, retry_context, :configuration => configuration)
end
end

[
{ "retry" => true, "retry_count" => 0 },
{ "retry" => 2, "retry_count" => 0 },
{ "retry" => 2, "retry_count" => 1 }
].each do |retry_context|
it "does not capture an exception when retries are remaining" do
exception = RetryableError.new

expect(Raven).not_to receive(:capture_exception)
Raven::SidekiqErrorHandler.new.call(exception, retry_context, :configuration => configuration)
end
end
end
end

class HappyWorker
Expand Down