Skip to content

Commit

Permalink
Faktory Plugin (honeybadger-io#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
rabidpraxis authored Jan 29, 2020
1 parent cdaabf0 commit c27f1bf
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ CHANGELOG](http://keepachangelog.com/) for how to update this file. This project
adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Changed
- Added Faktory plugin -@scottrobertson

## [4.5.6] - 2020-01-08
### Fixed
Expand Down
5 changes: 5 additions & 0 deletions lib/honeybadger/config/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ class Boolean; end
default: 0,
type: Integer
},
:'faktory.attempt_threshold' => {
description: 'The number of attempts before notifications will be sent.',
default: 0,
type: Integer
},
:'sidekiq.use_component' => {
description: 'Automatically set the component to the class of the job. Helps with grouping.',
default: true,
Expand Down
52 changes: 52 additions & 0 deletions lib/honeybadger/plugins/faktory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'honeybadger/plugin'
require 'honeybadger/ruby'

module Honeybadger
module Plugins
module Faktory
class Middleware
def call(worker, job)
Honeybadger.clear!
yield
end
end

Plugin.register do
requirement { defined?(::Faktory) }

execution do
::Faktory.configure_worker do |faktory|
faktory.worker_middleware do |chain|
chain.prepend Middleware
end
end

::Faktory.configure_worker do |faktory|
faktory.error_handlers << lambda do |ex, params|
opts = {parameters: params}

if job = params[:job]
if (threshold = config[:'faktory.attempt_threshold'].to_i) > 0
# If job.failure is nil, it is the first attempt. The first
# retry has a job.failure.retry_count of 0, which would be
# the second attempt in our case.
retry_count = job.dig('failure', 'retry_count')
attempt = retry_count ? retry_count + 1 : 0

limit = [job['retry'].to_i, threshold].min

return if attempt < limit
end

opts[:component] = job['jobtype']
opts[:action] = 'perform'
end

Honeybadger.notify(ex, opts)
end
end
end
end
end
end
end
121 changes: 121 additions & 0 deletions spec/unit/honeybadger/plugins/faktory_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'honeybadger/plugins/faktory'
require 'honeybadger/config'

describe "Faktory Dependency" do
let(:config) { Honeybadger::Config.new(logger: NULL_LOGGER, debug: true) }

before do
Honeybadger::Plugin.instances[:faktory].reset!
end

context "when faktory is not installed" do
it "fails quietly" do
expect { Honeybadger::Plugin.instances[:faktory].load!(config) }.not_to raise_error
end
end

context "when faktory is installed" do
let(:shim) do
Class.new do
def self.configure_worker
end
end
end

let(:faktory_config) { double('config', :error_handlers => []) }
let(:chain) { double('chain', :prepend => true) }

before do
Object.const_set(:Faktory, shim)
allow(::Faktory).to receive(:configure_worker).and_yield(faktory_config)
allow(faktory_config).to receive(:worker_middleware).and_yield(chain)
end

after { Object.send(:remove_const, :Faktory) }

it "adds the error handler" do
Honeybadger::Plugin.instances[:faktory].load!(config)
expect(faktory_config.error_handlers).not_to be_empty
end

describe "error handler" do
let(:exception) { RuntimeError.new('boom') }

before do
Honeybadger::Plugin.instances[:faktory].load!(config)
end

context "not within job execution" do
let(:handler_context) { {context: 'Failed Hard', event: {} } }

it "notifies Honeybadger" do
expect(Honeybadger).to receive(:notify).with(exception, { parameters: handler_context }).once
faktory_config.error_handlers[0].call(exception, handler_context)
end
end

context "within job execution" do
let(:handler_context) { {context: 'Job raised exception', job: job } }
let(:job) { first_invocation }
let(:retried_invocation) { {'retry' => retry_limit, 'failure' => failure, 'jobtype' => 'JobType'} }
let(:first_invocation) { {'retry' => retry_limit, 'jobtype' => 'JobType'} }
let(:failure) { { 'retry_count' => attempt - 1 } }
let(:retry_limit) { 5 }
let(:attempt) { 0 }

let(:error_payload) {{
parameters: handler_context,
component: 'JobType',
action: 'perform'
}}

it "notifies Honeybadger" do
expect(Honeybadger).to receive(:notify).with(exception, error_payload).once
faktory_config.error_handlers[0].call(exception, handler_context)
end

context "when an attempt threshold is configured" do
let(:job) { retried_invocation }
let(:retry_limit) { 1 }
let(:attempt) { 0 }
let(:config) { Honeybadger::Config.new(logger: NULL_LOGGER, debug: true, :'faktory.attempt_threshold' => 5) }

it "doesn't notify Honeybadger" do
expect(Honeybadger).not_to receive(:notify)
faktory_config.error_handlers[0].call(exception, handler_context)
end

context "and the retry_limit is zero on first invocation" do
let(:job) { first_invocation }
let(:retry_limit) { 0 }

it "notifies Honeybadger" do
expect(Honeybadger).to receive(:notify).with(exception, error_payload).once
faktory_config.error_handlers[0].call(exception, handler_context)
end
end

context "and the retry_limit is exhausted" do
let(:attempt) { 3 }
let(:retry_limit) { 3 }

it "notifies Honeybadger" do
expect(Honeybadger).to receive(:notify).with(exception, error_payload).once
faktory_config.error_handlers[0].call(exception, handler_context)
end
end

context "and the attempts meets the threshold" do
let(:attempt) { 5 }
let(:retry_limit) { 10 }

it "notifies Honeybadger" do
expect(Honeybadger).to receive(:notify).with(exception, error_payload).once
faktory_config.error_handlers[0].call(exception, handler_context)
end
end
end
end
end
end
end

0 comments on commit c27f1bf

Please sign in to comment.