Class: Wurk::Sentry::ErrorHandler
- Inherits:
-
Object
- Object
- Wurk::Sentry::ErrorHandler
- Defined in:
- lib/wurk/sentry/error_handler.rb
Overview
config.error_handlers entry. Reports fetch-loop errors
(context: "Error fetching job"), shutdown-path errors ("!shutdown"),
unparseable payloads ("Invalid JSON"), the retry machinery's own
meta-errors, and job failures ("Job raised exception").
A job failure is Middleware's to report whenever the exception passed through it: the middleware holds the job's scope and has already applied the terminal-attempt policy, so reporting it here too would double it. What reaches this handler unseen raised outside the middleware — the class failed to load, the reloader or an earlier middleware raised — and gets the same policy and scope here.
Handler signature is Sidekiq's: call(exception, context_hash, config).
Constant Summary collapse
- JOB_FAILURE =
'Job raised exception'- SEEN_BY_MIDDLEWARE =
Thread-local handshake with Middleware: the exception it last saw on this thread. Error handlers run synchronously on the processor thread that raised, so the slot is never read across threads.
:wurk_sentry_seen_by_middleware- REPORTED_ON_THREAD =
The exception this thread last captured, for DeathHandler's dedupe.
:wurk_sentry_reported_on_thread- DEFAULT_FILTERED_ERROR_CLASSES =
Transport blips the pool already retried before re-raising. Wurk's default handler logs these at WARN precisely because they are self-healing, and the fetch loop runs them in a tight
sleep(1)cycle: on one production Dragonfly backend a single Sentry issue accumulated ~136,000 events from fetch blips while the job pipeline was completely healthy. They stay in the logs; they just stop paging anyone.Deliberately not
RedisClient::Error: the server answering MISCONF, OOM, READONLY, NOPERM or WRONGPASS is not a blip. It heals only when someone acts, and every write fails until then, so those are reported (rate-limited below). [RedisClient::ConnectionError, ConnectionPool::TimeoutError].freeze
Instance Attribute Summary collapse
-
#filtered_error_classes ⇒ Object
readonly
Returns the value of attribute filtered_error_classes.
Class Method Summary collapse
- .forget_thread! ⇒ Object
- .reported!(exception) ⇒ Object
- .reported?(exception) ⇒ Boolean
- .seen_by_middleware!(exception) ⇒ Object
Instance Method Summary collapse
- #call(exception, context = {}, config = nil) ⇒ Object
- #filtered?(exception) ⇒ Boolean
-
#initialize(filter_transport_errors: true, filtered_error_classes: nil, report_after_attempts: nil, coalescer: ErrorCoalescer.new) ⇒ ErrorHandler
constructor
A new instance of ErrorHandler.
Constructor Details
#initialize(filter_transport_errors: true, filtered_error_classes: nil, report_after_attempts: nil, coalescer: ErrorCoalescer.new) ⇒ ErrorHandler
Returns a new instance of ErrorHandler.
65 66 67 68 69 70 71 72 |
# File 'lib/wurk/sentry/error_handler.rb', line 65 def initialize(filter_transport_errors: true, filtered_error_classes: nil, report_after_attempts: nil, coalescer: ErrorCoalescer.new) @filter_transport_errors = filter_transport_errors @default_filter = filtered_error_classes.nil? @filtered_error_classes = (filtered_error_classes || DEFAULT_FILTERED_ERROR_CLASSES).to_a.freeze @report_after_attempts = report_after_attempts @coalescer = coalescer end |
Instance Attribute Details
#filtered_error_classes ⇒ Object (readonly)
Returns the value of attribute filtered_error_classes.
63 64 65 |
# File 'lib/wurk/sentry/error_handler.rb', line 63 def filtered_error_classes @filtered_error_classes end |
Class Method Details
.forget_thread! ⇒ Object
45 46 47 48 |
# File 'lib/wurk/sentry/error_handler.rb', line 45 def self.forget_thread! Thread.current[SEEN_BY_MIDDLEWARE] = nil Thread.current[REPORTED_ON_THREAD] = nil end |
.reported!(exception) ⇒ Object
37 38 39 |
# File 'lib/wurk/sentry/error_handler.rb', line 37 def self.reported!(exception) Thread.current[REPORTED_ON_THREAD] = exception end |
.reported?(exception) ⇒ Boolean
41 42 43 |
# File 'lib/wurk/sentry/error_handler.rb', line 41 def self.reported?(exception) Thread.current[REPORTED_ON_THREAD].equal?(exception) end |
.seen_by_middleware!(exception) ⇒ Object
30 31 32 |
# File 'lib/wurk/sentry/error_handler.rb', line 30 def self.seen_by_middleware!(exception) Thread.current[SEEN_BY_MIDDLEWARE] = exception end |
Instance Method Details
#call(exception, context = {}, config = nil) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/wurk/sentry/error_handler.rb', line 74 def call(exception, context = {}, config = nil) return nil unless Wurk::Sentry.enabled? return nil if control_flow?(exception) # Ahead of the transport filter: that filter quiets the infrastructure # loop, and a job that died on a Redis error is still a dead job. return capture_job_failure(exception, context, config) if job_failure?(context) return nil if filtered?(exception) extra = extra_for(context) if exception.is_a?(RedisClient::HasCommand) suppressed = @coalescer.admit(command_error_key(exception)) return nil unless suppressed extra[:suppressed] = suppressed if suppressed.positive? end ::Sentry.capture_exception(exception, extra: extra, tags: (context)) nil end |
#filtered?(exception) ⇒ Boolean
93 94 95 96 97 98 99 100 101 |
# File 'lib/wurk/sentry/error_handler.rb', line 93 def filtered?(exception) return false unless @filter_transport_errors # READONLY and MASTERDOWN subclass ConnectionError, but the server did answer, # so the default passes them. A list the host wrote is taken at its word. return false if @default_filter && exception.is_a?(RedisClient::HasCommand) @filtered_error_classes.any? { |klass| exception.is_a?(klass) } end |