diff --git a/Changes.md b/Changes.md index 716a1fcc..2f8e6a19 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,16 @@ 1.2.0 ----------- +- Error backtraces can optionally be stored as part of the retry, + for display in the web UI if you aren't using an error service. [#155] + +```ruby +class Worker + include Sidekiq::Worker + sidekiq_options :backtrace => true || 10 +end +``` + - Add Timeout middleware to optionally kill a worker after N seconds, just configure like so. (blackgold9) diff --git a/lib/sidekiq/client.rb b/lib/sidekiq/client.rb index cbddabfb..45d1295e 100644 --- a/lib/sidekiq/client.rb +++ b/lib/sidekiq/client.rb @@ -26,6 +26,7 @@ module Sidekiq # class - the worker class to call, required # args - an array of simple arguments to the perform method, must be JSON-serializable # retry - whether to retry this job if it fails, true or false, default true + # backtrace - whether to save any error backtrace, default false # # All options must be strings, not symbols. NB: because we are serializing to JSON, all # symbols in 'args' will be converted to strings. @@ -43,6 +44,10 @@ module Sidekiq item['retry'] = !!worker_class.get_sidekiq_options['retry'] queue = item['queue'] || worker_class.get_sidekiq_options['queue'] || 'default' + if !item['backtrace'] && worker_class.get_sidekiq_options['backtrace'] + item['backtrace'] = worker_class.get_sidekiq_options['backtrace'] + end + if !item['timeout'] && worker_class.get_sidekiq_options['timeout'] item['timeout'] = worker_class.get_sidekiq_options['timeout'] end diff --git a/lib/sidekiq/middleware/server/retry_jobs.rb b/lib/sidekiq/middleware/server/retry_jobs.rb index 8f7ec36a..0dee9419 100644 --- a/lib/sidekiq/middleware/server/retry_jobs.rb +++ b/lib/sidekiq/middleware/server/retry_jobs.rb @@ -42,6 +42,12 @@ module Sidekiq msg['retry_count'] = 0 end + if msg['backtrace'] == true + msg['error_backtrace'] = e.backtrace + elsif msg['backtrace'].to_i != 0 + msg['error_backtrace'] = e.backtrace[0..msg['backtrace'].to_i] + end + if count <= MAX_COUNT delay = DELAY.call(count) logger.debug { "Failure! Retry #{count} in #{delay} seconds" } diff --git a/lib/sidekiq/worker.rb b/lib/sidekiq/worker.rb index 073722eb..7e25d781 100644 --- a/lib/sidekiq/worker.rb +++ b/lib/sidekiq/worker.rb @@ -37,6 +37,8 @@ module Sidekiq # :queue - use a named queue for this Worker, default 'default' # :retry - enable the RetryJobs middleware for this Worker, default *true* # :timeout - timeout the perform method after N seconds, default *nil* + # :backtrace - whether to save any error backtrace in the retry payload to display in web UI, + # can be true, false or an integer number of lines to save, default *false* def sidekiq_options(opts={}) @sidekiq_options = get_sidekiq_options.merge(stringify_keys(opts || {})) end diff --git a/test/test_retry.rb b/test/test_retry.rb index 96a045cb..16730419 100644 --- a/test/test_retry.rb +++ b/test/test_retry.rb @@ -25,6 +25,34 @@ class TestRetry < MiniTest::Unit::TestCase assert_equal msg, msg2 end + it 'saves backtraces' do + @redis.expect :zadd, 1, ['retry', String, String] + msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => true } + handler = Sidekiq::Middleware::Server::RetryJobs.new + c = nil + assert_raises RuntimeError do + handler.call('', msg, 'default') do + c = caller(0); raise "kerblammo!" + end + end + assert msg["error_backtrace"] + assert_equal c, msg["error_backtrace"] + end + + it 'saves partial backtraces' do + @redis.expect :zadd, 1, ['retry', String, String] + msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => 3 } + handler = Sidekiq::Middleware::Server::RetryJobs.new + c = nil + assert_raises RuntimeError do + handler.call('', msg, 'default') do + c = caller(0)[0..3]; raise "kerblammo!" + end + end + assert msg["error_backtrace"] + assert_equal c, msg["error_backtrace"] + end + it 'handles a new failed message' do @redis.expect :zadd, 1, ['retry', String, String] msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true } @@ -38,6 +66,7 @@ class TestRetry < MiniTest::Unit::TestCase assert_equal 'kerblammo!', msg["error_message"] assert_equal 'RuntimeError', msg["error_class"] assert_equal 0, msg["retry_count"] + refute msg["error_backtrace"] assert msg["failed_at"] @redis.verify end diff --git a/web/views/retry.slim b/web/views/retry.slim index 990f501e..17fcb277 100644 --- a/web/views/retry.slim +++ b/web/views/retry.slim @@ -23,6 +23,10 @@ header tr th Error Message td= msg['error_message'] + - if !msg['error_backtrace'].nil? + tr + th Error Backtrace + td== msg['error_backtrace'].join("
") - if msg['retry_count'] > 0 tr th Retry Count