sidekiq-failures 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile +2 -0
- data/lib/sidekiq/failures.rb +10 -17
- data/lib/sidekiq/failures/failure_set.rb +22 -0
- data/lib/sidekiq/failures/locales/en.yml +5 -0
- data/lib/sidekiq/failures/middleware.rb +37 -19
- data/lib/sidekiq/failures/sorted_entry.rb +14 -0
- data/lib/sidekiq/failures/version.rb +1 -1
- data/lib/sidekiq/failures/views/failure.erb +30 -0
- data/lib/sidekiq/failures/views/failures.erb +69 -52
- data/lib/sidekiq/failures/web_extension.rb +54 -6
- data/sidekiq-failures.gemspec +1 -1
- data/test/middleware_test.rb +3 -3
- data/test/web_extension_test.rb +116 -19
- metadata +23 -20
- data/Gemfile.lock +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e3a32d193a70834983a509cda264943a04cf76d
|
4
|
+
data.tar.gz: 946c4d36eab67ffd4b16b1e4e5dece3699be0f5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 322bccce2ceacf773accdc94f9f693fc4c74286308aa2470cde98b731fedace3497514114161851194b074c44aa09179c36cc7824523297ad7b31da0480b2a05
|
7
|
+
data.tar.gz: ef0caebd8929b2f98bb1a7956bfea0b9806198bb72f29d3ee971e7381d44b68b8fea6766aba82c8c3813ec0ad41d9b4ea05b5da773aea4d9d60bd913922c082a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
## 0.4.0
|
4
|
+
* Bump sidekiq dependency to sidekiq >= 2.16.0
|
5
|
+
* Introduce delete(all) / retry(all) (@spectator)
|
6
|
+
* Fix Sidekiq 3 compatibility (@petergoldstein)
|
7
|
+
* Sidekiq 3 compatibility cleanup (@spectator)
|
8
|
+
* Explicitly require sidekiq/api (@krasnoukhov)
|
9
|
+
|
3
10
|
## 0.3.0
|
4
11
|
* Bump sidekiq dependency to sidekiq >= 2.14.0
|
5
12
|
* Remove slim templates and dependecy
|
@@ -8,7 +15,7 @@
|
|
8
15
|
* Add `Sidekiq::Failures.count` helper method (@zanker)
|
9
16
|
* Adhere to sidekiq approach of showing UTC times
|
10
17
|
* Catch all exceptions, not just those that inherit from StandardError (@tylerkovacs)
|
11
|
-
* Fix
|
18
|
+
* Fix private method call (@bwthomas)
|
12
19
|
|
13
20
|
## 0.2.2
|
14
21
|
* Support ERB for sidekiq >= 2.14.0 (@tobiassvn)
|
data/Gemfile
CHANGED
data/lib/sidekiq/failures.rb
CHANGED
@@ -4,7 +4,10 @@ rescue LoadError
|
|
4
4
|
# client-only usage
|
5
5
|
end
|
6
6
|
|
7
|
+
require "sidekiq/api"
|
7
8
|
require "sidekiq/failures/version"
|
9
|
+
require "sidekiq/failures/sorted_entry"
|
10
|
+
require "sidekiq/failures/failure_set"
|
8
11
|
require "sidekiq/failures/middleware"
|
9
12
|
require "sidekiq/failures/web_extension"
|
10
13
|
|
@@ -50,37 +53,27 @@ module Sidekiq
|
|
50
53
|
end
|
51
54
|
|
52
55
|
module Failures
|
53
|
-
|
54
56
|
LIST_KEY = :failed
|
55
57
|
|
56
|
-
def self.reset_failures
|
57
|
-
Sidekiq.redis { |c|
|
58
|
-
c.multi do
|
59
|
-
c.del(LIST_KEY)
|
60
|
-
c.set("stat:failed", 0) if options[:counter] || options["counter"]
|
61
|
-
end
|
62
|
-
}
|
58
|
+
def self.reset_failures
|
59
|
+
Sidekiq.redis { |c| c.set("stat:failed", 0) }
|
63
60
|
end
|
64
61
|
|
65
62
|
def self.count
|
66
|
-
Sidekiq.redis {|r| r.
|
63
|
+
Sidekiq.redis {|r| r.zcard(LIST_KEY) }
|
67
64
|
end
|
68
65
|
end
|
69
66
|
end
|
70
67
|
|
71
68
|
Sidekiq.configure_server do |config|
|
72
69
|
config.server_middleware do |chain|
|
73
|
-
chain.
|
70
|
+
chain.insert_before Sidekiq::Middleware::Server::RetryJobs,
|
71
|
+
Sidekiq::Failures::Middleware
|
74
72
|
end
|
75
73
|
end
|
76
74
|
|
77
75
|
if defined?(Sidekiq::Web)
|
78
76
|
Sidekiq::Web.register Sidekiq::Failures::WebExtension
|
79
|
-
|
80
|
-
|
81
|
-
# For sidekiq < 2.5
|
82
|
-
Sidekiq::Web.tabs << "failures"
|
83
|
-
else
|
84
|
-
Sidekiq::Web.tabs["Failures"] = "failures"
|
85
|
-
end
|
77
|
+
Sidekiq::Web.tabs["Failures"] = "failures"
|
78
|
+
Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "failures/locales")
|
86
79
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Failures
|
3
|
+
Superclass =
|
4
|
+
if defined?(Sidekiq::JobSet)
|
5
|
+
Sidekiq::JobSet
|
6
|
+
else
|
7
|
+
Sidekiq::SortedSet
|
8
|
+
end
|
9
|
+
|
10
|
+
class FailureSet < Superclass
|
11
|
+
def initialize
|
12
|
+
super LIST_KEY
|
13
|
+
end
|
14
|
+
|
15
|
+
def retry_all_failures
|
16
|
+
while size > 0
|
17
|
+
each(&:retry_failure)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -2,7 +2,6 @@ module Sidekiq
|
|
2
2
|
module Failures
|
3
3
|
|
4
4
|
class Middleware
|
5
|
-
include Sidekiq::Util
|
6
5
|
attr_accessor :msg
|
7
6
|
|
8
7
|
def call(worker, msg, queue)
|
@@ -13,21 +12,24 @@ module Sidekiq
|
|
13
12
|
rescue Exception => e
|
14
13
|
raise e if skip_failure?
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
:error => e.message,
|
21
|
-
:backtrace => e.backtrace,
|
22
|
-
:worker => msg['class'],
|
23
|
-
:processor => "#{hostname}:#{process_id}-#{Thread.current.object_id}",
|
24
|
-
:queue => queue
|
25
|
-
}
|
15
|
+
msg['error_message'] = e.message
|
16
|
+
msg['error_class'] = e.class.name
|
17
|
+
msg['processor'] = identity
|
18
|
+
msg['failed_at'] = Time.now.utc.to_f
|
26
19
|
|
20
|
+
if msg['backtrace'] == true
|
21
|
+
msg['error_backtrace'] = e.backtrace
|
22
|
+
elsif msg['backtrace'] == false
|
23
|
+
# do nothing
|
24
|
+
elsif msg['backtrace'].to_i != 0
|
25
|
+
msg['error_backtrace'] = e.backtrace[0..msg['backtrace'].to_i]
|
26
|
+
end
|
27
|
+
|
28
|
+
payload = Sidekiq.dump_json(msg)
|
27
29
|
Sidekiq.redis do |conn|
|
28
|
-
conn.
|
30
|
+
conn.zadd(LIST_KEY, Time.now.utc.to_f, payload)
|
29
31
|
unless Sidekiq.failures_max_count == false
|
30
|
-
conn.
|
32
|
+
conn.zremrangebyrank(LIST_KEY, 0, -(Sidekiq.failures_max_count + 1))
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -36,12 +38,16 @@ module Sidekiq
|
|
36
38
|
|
37
39
|
private
|
38
40
|
|
39
|
-
def
|
40
|
-
failure_mode == :off
|
41
|
+
def failure_mode_off?
|
42
|
+
failure_mode == :off
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
44
|
-
failure_mode == :exhausted
|
45
|
+
def failure_mode_exhausted?
|
46
|
+
failure_mode == :exhausted
|
47
|
+
end
|
48
|
+
|
49
|
+
def skip_failure?
|
50
|
+
failure_mode_off? || failure_mode_exhausted? && !exhausted?
|
45
51
|
end
|
46
52
|
|
47
53
|
def failure_mode
|
@@ -57,8 +63,12 @@ module Sidekiq
|
|
57
63
|
end
|
58
64
|
end
|
59
65
|
|
60
|
-
def
|
61
|
-
!
|
66
|
+
def exhausted?
|
67
|
+
!retriable? || retry_count >= max_retries
|
68
|
+
end
|
69
|
+
|
70
|
+
def retriable?
|
71
|
+
msg['retry']
|
62
72
|
end
|
63
73
|
|
64
74
|
def retry_count
|
@@ -76,6 +86,14 @@ module Sidekiq
|
|
76
86
|
def default_max_retries
|
77
87
|
Sidekiq::Middleware::Server::RetryJobs::DEFAULT_MAX_RETRY_ATTEMPTS
|
78
88
|
end
|
89
|
+
|
90
|
+
def hostname
|
91
|
+
Socket.gethostname
|
92
|
+
end
|
93
|
+
|
94
|
+
def identity
|
95
|
+
@@identity ||= "#{hostname}:#{$$}"
|
96
|
+
end
|
79
97
|
end
|
80
98
|
end
|
81
99
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
class SortedEntry
|
3
|
+
def retry_failure
|
4
|
+
Sidekiq.redis do |conn|
|
5
|
+
results = conn.zrangebyscore(Sidekiq::Failures::LIST_KEY, score, score)
|
6
|
+
conn.zremrangebyscore(Sidekiq::Failures::LIST_KEY, score, score)
|
7
|
+
results.map do |message|
|
8
|
+
msg = Sidekiq.load_json(message)
|
9
|
+
Sidekiq::Client.push(msg)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%= erb :_job_info, :locals => {:job => @failure, :type => :failure} %>
|
2
|
+
|
3
|
+
<h3><%= t('Error') %></h3>
|
4
|
+
<table class="error table table-bordered table-striped">
|
5
|
+
<tbody>
|
6
|
+
<tr>
|
7
|
+
<th><%= t('ErrorClass') %></th>
|
8
|
+
<td>
|
9
|
+
<code><%= @failure['error_class'] %></code>
|
10
|
+
</td>
|
11
|
+
</tr>
|
12
|
+
<tr>
|
13
|
+
<th><%= t('ErrorMessage') %></th>
|
14
|
+
<td><%= @failure['error_message'] %></td>
|
15
|
+
</tr>
|
16
|
+
<% if !@failure['error_backtrace'].nil? %>
|
17
|
+
<tr>
|
18
|
+
<th><%= t('ErrorBacktrace') %></th>
|
19
|
+
<td>
|
20
|
+
<code><%= @failure['error_backtrace'].join("<br/>") %></code>
|
21
|
+
</td>
|
22
|
+
</tr>
|
23
|
+
<% end %>
|
24
|
+
</tbody>
|
25
|
+
</table>
|
26
|
+
<form class="form-horizontal" action="<%= root_path %>failures/<%= job_params(@failure, @failure.score) %>" method="post">
|
27
|
+
<a class="btn" href="<%= root_path %>failures"><%= t('GoBack') %></a>
|
28
|
+
<input class="btn btn-primary" type="submit" name="retry" value="<%= t('RetryNow') %>" />
|
29
|
+
<input class="btn btn-danger" type="submit" name="delete" value="<%= t('Delete') %>" />
|
30
|
+
</form>
|
@@ -1,58 +1,75 @@
|
|
1
1
|
<header class="row">
|
2
|
-
<div class="
|
3
|
-
<h3
|
4
|
-
</div>
|
5
|
-
<div class="span4">
|
6
|
-
<% if @messages.size > 0 %>
|
7
|
-
<%= erb :_paging, :locals => { :url => "#{root_path}failures#@name" } %>
|
8
|
-
<% end %>
|
2
|
+
<div class="col-sm-5">
|
3
|
+
<h3><%= t('FailedJobs') %></h3>
|
9
4
|
</div>
|
5
|
+
<% if @failures.size > 0 && @total_size > @count %>
|
6
|
+
<div class="col-sm-4">
|
7
|
+
<%= erb :_paging, :locals => { :url => "#{root_path}failures" } %>
|
8
|
+
</div>
|
9
|
+
<% end %>
|
10
|
+
<%= filtering('failures') if respond_to?(:filtering) %>
|
10
11
|
</header>
|
11
12
|
|
12
|
-
<% if @
|
13
|
-
<
|
14
|
-
<
|
15
|
-
<
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
<
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
13
|
+
<% if @failures.size > 0 %>
|
14
|
+
<form action="<%= root_path %>failures" method="post">
|
15
|
+
<table class="table table-striped table-bordered table-white">
|
16
|
+
<thead>
|
17
|
+
<tr>
|
18
|
+
<th width="20px" class="table-checkbox">
|
19
|
+
<label>
|
20
|
+
<input type="checkbox" class="check_all" />
|
21
|
+
</label>
|
22
|
+
</th>
|
23
|
+
<th style="width: 20%"><%= t('Worker') %></th>
|
24
|
+
<th style="width: 10%"><%= t('Arguments') %></th>
|
25
|
+
<th style="width: 10%"><%= t('Queue') %></th>
|
26
|
+
<th style="width: 10%"><%= t('FailedAt') %></th>
|
27
|
+
<th style="width: 50%"><%= t('Error') %></th>
|
28
|
+
</tr>
|
29
|
+
</thead>
|
30
|
+
<% @failures.each do |entry| %>
|
31
|
+
<tr>
|
32
|
+
<td class="table-checkbox">
|
33
|
+
<label>
|
34
|
+
<input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' />
|
35
|
+
</label>
|
36
|
+
</td>
|
37
|
+
<td><a href="<%= root_path %>failures/<%= job_params(entry.item, entry.score) %>"><%= entry.klass %></a></td>
|
38
|
+
<td>
|
39
|
+
<div class="args"><%= display_args(entry.args) %></div>
|
40
|
+
</td>
|
41
|
+
<td>
|
42
|
+
<a href="<%= root_path %>queues/<%= entry.queue %>"><%= entry.queue %></a>
|
43
|
+
</td>
|
44
|
+
<td><%= relative_time(Time.at(entry['failed_at'])) %></td>
|
45
|
+
<td style="overflow: auto; padding: 10px;">
|
46
|
+
<a class="backtrace" href="#" onclick="$(this).next().toggle(); return false">
|
47
|
+
<%= h entry['error_class'] %>: <%= h entry['error_message'] %>
|
48
|
+
</a>
|
49
|
+
<pre style="display: none; background: none; border: 0; width: 100%; max-height: 30em; font-size: 0.8em; white-space: nowrap;">
|
50
|
+
<%= entry['error_backtrace'].join("<br />") if entry['error_backtrace'] %>
|
51
|
+
</pre>
|
52
|
+
<p>
|
53
|
+
<span>Processor: <%= entry['processor'] %></span>
|
54
|
+
</p>
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
<% end %>
|
58
|
+
</table>
|
59
|
+
<input class="btn btn-primary btn-xs pull-left" type="submit" name="retry" value="<%= t('RetryNow') %>" />
|
60
|
+
<input class="btn btn-danger btn-xs pull-left" type="submit" name="delete" value="<%= t('Delete') %>" />
|
61
|
+
</form>
|
62
|
+
|
63
|
+
<form action="<%= root_path %>failures/all/reset" method="post">
|
64
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="reset" value="<%= t('Reset Counter') %>" data-confirm="<%= t('AreYouSure') %>" />
|
65
|
+
</form>
|
66
|
+
<form action="<%= root_path %>failures/all/delete" method="post">
|
67
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
68
|
+
</form>
|
69
|
+
<form action="<%= root_path %>failures/all/retry" method="post">
|
70
|
+
<input class="btn btn-danger btn-xs pull-right" type="submit" name="retry" value="<%= t('RetryAll') %>" data-confirm="<%= t('AreYouSure') %>" />
|
71
|
+
</form>
|
72
|
+
|
56
73
|
<% else %>
|
57
|
-
<div class="alert alert-success"
|
74
|
+
<div class="alert alert-success"><%= t('NoFailedJobsFound') %></div>
|
58
75
|
<% end %>
|
@@ -3,19 +3,67 @@ module Sidekiq
|
|
3
3
|
module WebExtension
|
4
4
|
|
5
5
|
def self.registered(app)
|
6
|
-
|
7
|
-
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
6
|
+
view_path = File.join(File.expand_path("..", __FILE__), "views")
|
8
7
|
|
8
|
+
app.get "/failures" do
|
9
9
|
@count = (params[:count] || 25).to_i
|
10
|
-
(@current_page, @total_size, @
|
11
|
-
@
|
10
|
+
(@current_page, @total_size, @failures) = page(LIST_KEY, params[:page], @count)
|
11
|
+
@failures = @failures.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
|
12
12
|
|
13
13
|
render(:erb, File.read(File.join(view_path, "failures.erb")))
|
14
14
|
end
|
15
15
|
|
16
|
-
app.
|
17
|
-
|
16
|
+
app.get "/failures/:key" do
|
17
|
+
halt 404 unless params['key']
|
18
|
+
|
19
|
+
@failure = FailureSet.new.fetch(*parse_params(params['key'])).first
|
20
|
+
redirect "#{root_path}failures" if @failure.nil?
|
21
|
+
render(:erb, File.read(File.join(view_path, "failure.erb")))
|
22
|
+
end
|
23
|
+
|
24
|
+
app.post "/failures" do
|
25
|
+
halt 404 unless params['key']
|
26
|
+
|
27
|
+
params['key'].each do |key|
|
28
|
+
job = FailureSet.new.fetch(*parse_params(key)).first
|
29
|
+
next unless job
|
30
|
+
|
31
|
+
if params['retry']
|
32
|
+
job.retry_failure
|
33
|
+
elsif params['delete']
|
34
|
+
job.delete
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
redirect_with_query("#{root_path}failures")
|
39
|
+
end
|
40
|
+
|
41
|
+
app.post "/failures/:key" do
|
42
|
+
halt 404 unless params['key']
|
43
|
+
|
44
|
+
job = FailureSet.new.fetch(*parse_params(params['key'])).first
|
45
|
+
if job
|
46
|
+
if params['retry']
|
47
|
+
job.retry_failure
|
48
|
+
elsif params['delete']
|
49
|
+
job.delete
|
50
|
+
end
|
51
|
+
end
|
52
|
+
redirect_with_query("#{root_path}failures")
|
53
|
+
end
|
54
|
+
|
55
|
+
app.post "/failures/all/reset" do
|
56
|
+
Sidekiq::Failures.reset_failures
|
57
|
+
redirect "#{root_path}failures"
|
58
|
+
end
|
59
|
+
|
60
|
+
app.post "/failures/all/delete" do
|
61
|
+
FailureSet.new.clear
|
62
|
+
redirect "#{root_path}failures"
|
63
|
+
end
|
18
64
|
|
65
|
+
app.post "/failures/all/retry" do
|
66
|
+
FailureSet.new.retry_all_failures
|
19
67
|
redirect "#{root_path}failures"
|
20
68
|
end
|
21
69
|
end
|
data/sidekiq-failures.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Sidekiq::Failures::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency "sidekiq", ">= 2.
|
18
|
+
gem.add_dependency "sidekiq", ">= 2.16.0"
|
19
19
|
|
20
20
|
gem.add_development_dependency "rake"
|
21
21
|
gem.add_development_dependency "rack-test"
|
data/test/middleware_test.rb
CHANGED
@@ -148,7 +148,7 @@ module Sidekiq
|
|
148
148
|
end
|
149
149
|
|
150
150
|
it "records failure if failing last retry and configured to track exhaustion" do
|
151
|
-
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' =>
|
151
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' => 25, 'failures' => 'exhausted')
|
152
152
|
|
153
153
|
assert_equal 0, failures_count
|
154
154
|
|
@@ -206,7 +206,7 @@ module Sidekiq
|
|
206
206
|
it "records failure if failing last retry and configured to track exhaustion by default" do
|
207
207
|
Sidekiq.failures_default_mode = 'exhausted'
|
208
208
|
|
209
|
-
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' =>
|
209
|
+
msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry' => true, 'retry_count' => 25)
|
210
210
|
|
211
211
|
assert_equal 0, failures_count
|
212
212
|
|
@@ -272,7 +272,7 @@ module Sidekiq
|
|
272
272
|
end
|
273
273
|
|
274
274
|
def failures_count
|
275
|
-
Sidekiq.redis { |conn|conn.
|
275
|
+
Sidekiq.redis { |conn| conn.zcard(LIST_KEY) }
|
276
276
|
end
|
277
277
|
|
278
278
|
def create_work(msg)
|
data/test/web_extension_test.rb
CHANGED
@@ -46,19 +46,38 @@ module Sidekiq
|
|
46
46
|
last_response.body.wont_match /No failed jobs found/
|
47
47
|
end
|
48
48
|
|
49
|
-
it 'has the
|
50
|
-
last_response.body.must_match /failures\/
|
51
|
-
last_response.body.must_match /
|
52
|
-
last_response.body.must_match /Clear All/
|
53
|
-
last_response.body.must_match /reset failed counter/
|
49
|
+
it 'has the reset counter form and action' do
|
50
|
+
last_response.body.must_match /failures\/all\/reset/
|
51
|
+
last_response.body.must_match /Reset Counter/
|
54
52
|
end
|
55
53
|
|
56
|
-
it 'can
|
54
|
+
it 'can reset counter' do
|
57
55
|
assert_equal failed_count, "1"
|
58
56
|
|
59
57
|
last_response.body.must_match /HardWorker/
|
60
58
|
|
61
|
-
post '/failures/
|
59
|
+
post '/failures/all/reset'
|
60
|
+
last_response.status.must_equal 302
|
61
|
+
last_response.location.must_match /failures$/
|
62
|
+
|
63
|
+
get '/failures'
|
64
|
+
last_response.status.must_equal 200
|
65
|
+
last_response.body.must_match /HardWorker/
|
66
|
+
|
67
|
+
assert_equal failed_count, "0"
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'has the delete all form and action' do
|
71
|
+
last_response.body.must_match /failures\/all\/delete/
|
72
|
+
last_response.body.must_match /Delete All/
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'can delete all failures' do
|
76
|
+
assert_equal failed_count, "1"
|
77
|
+
|
78
|
+
last_response.body.must_match /HardWorker/
|
79
|
+
|
80
|
+
post '/failures/all/delete'
|
62
81
|
last_response.status.must_equal 302
|
63
82
|
last_response.location.must_match /failures$/
|
64
83
|
|
@@ -69,37 +88,111 @@ module Sidekiq
|
|
69
88
|
assert_equal failed_count, "1"
|
70
89
|
end
|
71
90
|
|
72
|
-
it '
|
91
|
+
it 'has the retry all form and action' do
|
92
|
+
last_response.body.must_match /failures\/all\/retry/
|
93
|
+
last_response.body.must_match /Retry All/
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'can retry all failures' do
|
97
|
+
assert_equal failed_count, "1"
|
98
|
+
|
99
|
+
last_response.body.must_match /HardWorker/
|
100
|
+
post '/failures/all/retry'
|
101
|
+
last_response.status.must_equal 302
|
102
|
+
last_response.location.must_match /failures/
|
103
|
+
|
104
|
+
get '/failures'
|
105
|
+
last_response.status.must_equal 200
|
106
|
+
last_response.body.must_match(/No failed jobs found/)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'can delete failure from the list' do
|
73
110
|
assert_equal failed_count, "1"
|
74
111
|
|
75
112
|
last_response.body.must_match /HardWorker/
|
76
113
|
|
77
|
-
post '/failures
|
114
|
+
post '/failures', { :key => [failure_score], :delete => 'Delete' }
|
78
115
|
last_response.status.must_equal 302
|
79
|
-
last_response.location.must_match /failures
|
116
|
+
last_response.location.must_match /failures/
|
80
117
|
|
81
118
|
get '/failures'
|
82
119
|
last_response.status.must_equal 200
|
83
120
|
last_response.body.must_match /No failed jobs found/
|
121
|
+
end
|
84
122
|
|
85
|
-
|
123
|
+
it 'can retry failure from the list' do
|
124
|
+
assert_equal failed_count, "1"
|
125
|
+
|
126
|
+
last_response.body.must_match /HardWorker/
|
127
|
+
|
128
|
+
post '/failures', { :key => [failure_score], :retry => 'Retry Now' }
|
129
|
+
last_response.status.must_equal 302
|
130
|
+
last_response.location.must_match /failures/
|
131
|
+
|
132
|
+
get '/failures'
|
133
|
+
last_response.status.must_equal 200
|
134
|
+
last_response.body.must_match /No failed jobs found/
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'when there is failure' do
|
139
|
+
before do
|
140
|
+
create_sample_failure
|
141
|
+
get "/failures/#{failure_score}"
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should be successful' do
|
145
|
+
last_response.status.must_equal 200
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'can display failure page' do
|
149
|
+
last_response.body.must_match /Job/
|
150
|
+
last_response.body.must_match /HardWorker/
|
151
|
+
last_response.body.must_match /ArgumentError/
|
152
|
+
last_response.body.must_match /file1/
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'can delete failure' do
|
156
|
+
last_response.body.must_match /HardWorker/
|
157
|
+
|
158
|
+
post "/failures/#{failure_score}", :delete => 'Delete'
|
159
|
+
last_response.status.must_equal 302
|
160
|
+
last_response.location.must_match /failures/
|
161
|
+
|
162
|
+
get "/failures/#{failure_score}"
|
163
|
+
last_response.status.must_equal 302
|
164
|
+
last_response.location.must_match /failures/
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'can retry failure' do
|
168
|
+
last_response.body.must_match /HardWorker/
|
169
|
+
|
170
|
+
post "/failures/#{failure_score}", :retry => 'Retry Now'
|
171
|
+
last_response.status.must_equal 302
|
172
|
+
last_response.location.must_match /failures/
|
173
|
+
|
174
|
+
get "/failures/#{failure_score}"
|
175
|
+
last_response.status.must_equal 302
|
176
|
+
last_response.location.must_match /failures/
|
86
177
|
end
|
87
178
|
end
|
88
179
|
|
89
180
|
def create_sample_failure
|
90
181
|
data = {
|
91
|
-
:
|
92
|
-
:
|
93
|
-
:
|
94
|
-
:
|
95
|
-
:
|
96
|
-
:
|
97
|
-
:
|
182
|
+
:queue => 'default',
|
183
|
+
:class => 'HardWorker',
|
184
|
+
:args => ['bob', 5],
|
185
|
+
:jid => 1,
|
186
|
+
:enqueued_at => Time.now.utc.to_f,
|
187
|
+
:failed_at => Time.now.utc.to_f,
|
188
|
+
:error_class => 'ArgumentError',
|
189
|
+
:error_message => 'Some new message',
|
190
|
+
:error_backtrace => ["path/file1.rb", "path/file2.rb"]
|
98
191
|
}
|
99
192
|
|
100
193
|
Sidekiq.redis do |c|
|
101
194
|
c.multi do
|
102
|
-
c.
|
195
|
+
c.zadd(Sidekiq::Failures::LIST_KEY, failure_score, Sidekiq.dump_json(data))
|
103
196
|
c.set("stat:failed", 1)
|
104
197
|
end
|
105
198
|
end
|
@@ -108,5 +201,9 @@ module Sidekiq
|
|
108
201
|
def failed_count
|
109
202
|
Sidekiq.redis { |c| c.get("stat:failed") }
|
110
203
|
end
|
204
|
+
|
205
|
+
def failure_score
|
206
|
+
Time.at(1).to_f
|
207
|
+
end
|
111
208
|
end
|
112
209
|
end
|
metadata
CHANGED
@@ -1,83 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-failures
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcelo Silveira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.16.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.16.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rack-test
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: sprockets
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: sinatra
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
description: Keep track of Sidekiq failed jobs
|
@@ -87,18 +87,21 @@ executables: []
|
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
-
- .gitignore
|
91
|
-
- .travis.yml
|
90
|
+
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
92
92
|
- CHANGELOG.md
|
93
93
|
- Gemfile
|
94
|
-
- Gemfile.lock
|
95
94
|
- LICENSE
|
96
95
|
- README.md
|
97
96
|
- Rakefile
|
98
97
|
- lib/sidekiq-failures.rb
|
99
98
|
- lib/sidekiq/failures.rb
|
99
|
+
- lib/sidekiq/failures/failure_set.rb
|
100
|
+
- lib/sidekiq/failures/locales/en.yml
|
100
101
|
- lib/sidekiq/failures/middleware.rb
|
102
|
+
- lib/sidekiq/failures/sorted_entry.rb
|
101
103
|
- lib/sidekiq/failures/version.rb
|
104
|
+
- lib/sidekiq/failures/views/failure.erb
|
102
105
|
- lib/sidekiq/failures/views/failures.erb
|
103
106
|
- lib/sidekiq/failures/web_extension.rb
|
104
107
|
- sidekiq-failures.gemspec
|
@@ -114,17 +117,17 @@ require_paths:
|
|
114
117
|
- lib
|
115
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
119
|
requirements:
|
117
|
-
- -
|
120
|
+
- - ">="
|
118
121
|
- !ruby/object:Gem::Version
|
119
122
|
version: '0'
|
120
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
124
|
requirements:
|
122
|
-
- -
|
125
|
+
- - ">="
|
123
126
|
- !ruby/object:Gem::Version
|
124
127
|
version: '0'
|
125
128
|
requirements: []
|
126
129
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.2.2
|
128
131
|
signing_key:
|
129
132
|
specification_version: 4
|
130
133
|
summary: Keeps track of Sidekiq failed jobs and adds a tab to the Web UI to let you
|
data/Gemfile.lock
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
sidekiq-failures (0.3.0)
|
5
|
-
sidekiq (>= 2.14.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
celluloid (0.15.2)
|
11
|
-
timers (~> 1.1.0)
|
12
|
-
connection_pool (1.2.0)
|
13
|
-
hike (1.2.1)
|
14
|
-
json (1.8.1)
|
15
|
-
multi_json (1.7.3)
|
16
|
-
rack (1.4.1)
|
17
|
-
rack-protection (1.2.0)
|
18
|
-
rack
|
19
|
-
rack-test (0.6.2)
|
20
|
-
rack (>= 1.0)
|
21
|
-
rake (0.9.2.2)
|
22
|
-
redis (3.0.6)
|
23
|
-
redis-namespace (1.3.2)
|
24
|
-
redis (~> 3.0.4)
|
25
|
-
sidekiq (2.17.0)
|
26
|
-
celluloid (>= 0.15.2)
|
27
|
-
connection_pool (>= 1.0.0)
|
28
|
-
json
|
29
|
-
redis (>= 3.0.4)
|
30
|
-
redis-namespace (>= 1.3.1)
|
31
|
-
sinatra (1.3.3)
|
32
|
-
rack (~> 1.3, >= 1.3.6)
|
33
|
-
rack-protection (~> 1.2)
|
34
|
-
tilt (~> 1.3, >= 1.3.3)
|
35
|
-
sprockets (2.8.1)
|
36
|
-
hike (~> 1.2)
|
37
|
-
multi_json (~> 1.0)
|
38
|
-
rack (~> 1.0)
|
39
|
-
tilt (~> 1.1, != 1.3.0)
|
40
|
-
tilt (1.3.3)
|
41
|
-
timers (1.1.0)
|
42
|
-
|
43
|
-
PLATFORMS
|
44
|
-
ruby
|
45
|
-
|
46
|
-
DEPENDENCIES
|
47
|
-
rack-test
|
48
|
-
rake
|
49
|
-
sidekiq-failures!
|
50
|
-
sinatra
|
51
|
-
sprockets
|