Support job tags

This commit is contained in:
fatkodima 2019-09-22 00:03:59 +03:00 committed by Mike Perham
parent c0c1ab25e5
commit c428383523
15 changed files with 88 additions and 12 deletions

View File

@ -5,6 +5,17 @@
HEAD
---------
- Support ad-hoc job tags. You can tag your jobs with, for example, subdomain, tenant, country, locale, application,
version, user/client, "alpha/beta/pro/ent", types of jobs, teams/people responsible for jobs,
additional metadata, etc.
Tags are shown on different pages with job listings [fatkodima, #4280]
```ruby
class MyWorker
include Sidekiq::Worker
sidekiq_options tags: ['foo', 'bar']
...
end
```
- Get scheduled jobs in batches before pushing into specific queues.
This will decrease enqueueing time of scheduled jobs by a third. [fatkodima, #4273]
```

View File

@ -388,6 +388,10 @@ module Sidekiq
Time.at(self["created_at"] || self["enqueued_at"] || 0).utc
end
def tags
self["tags"] || []
end
def error_backtrace
# Cache nil values
if defined?(@error_backtrace)

View File

@ -223,6 +223,7 @@ module Sidekiq
raise(ArgumentError, "Job args must be an Array") unless item["args"].is_a?(Array)
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.key?("at") && !item["at"].is_a?(Numeric)
raise(ArgumentError, "Job tags must be an Array") if item["tags"] && !item["tags"].is_a?(Array)
# raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
normalized_hash(item["class"])

View File

@ -35,6 +35,7 @@ module Sidekiq
jid: job_hash["jid"],
}
h[:bid] = job_hash["bid"] if job_hash["bid"]
h[:tags] = job_hash["tags"] if job_hash["tags"]
h
end

View File

@ -36,7 +36,16 @@ module Sidekiq
end
def format_context
" " + ctx.compact.map { |k, v| "#{k}=#{v}" }.join(" ") if ctx.any?
if ctx.any?
" " + ctx.compact.map { |k, v|
case v
when Array
"#{k}=#{v.join(",")}"
else
"#{k}=#{v}"
end
}.join(" ")
end
end
end

View File

@ -249,7 +249,7 @@ module Sidekiq
queue class args retry_count retried_at failed_at
jid error_message error_class backtrace
error_backtrace enqueued_at retry wrapped
created_at
created_at tags
])
def retry_extra_items(retry_job)

View File

@ -212,6 +212,11 @@ describe 'API' do
include Sidekiq::Worker
end
class WorkerWithTags
include Sidekiq::Worker
sidekiq_options tags: ['foo']
end
it 'can enumerate jobs' do
q = Sidekiq::Queue.new
Time.stub(:now, Time.new(2012, 12, 26)) do
@ -295,6 +300,14 @@ describe 'API' do
assert_nil job.enqueued_at
end
it 'returns tags field for jobs' do
job_id = ApiWorker.perform_async
assert_equal [], Sidekiq::Queue.new.find_job(job_id).tags
job_id = WorkerWithTags.perform_async
assert_equal ['foo'], Sidekiq::Queue.new.find_job(job_id).tags
end
it 'can delete jobs' do
q = Sidekiq::Queue.new
ApiWorker.perform_async(1, 'mike')

View File

@ -32,6 +32,10 @@ describe Sidekiq::Client do
assert_raises ArgumentError do
Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1, 2], 'at' => [Time.now.to_f, :not_a_numeric])
end
assert_raises ArgumentError do
Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1], 'tags' => :not_an_array)
end
end
end

View File

@ -24,7 +24,7 @@ class TestJobLogger < Minitest::Test
# pretty
p = @logger.formatter = Sidekiq::Logger::Formatters::Pretty.new
job = {"jid"=>"1234abc", "wrapped"=>"FooWorker", "class"=>"Wrapper"}
job = {"jid"=>"1234abc", "wrapped"=>"FooWorker", "class"=>"Wrapper", "tags" => ["bar", "baz"]}
# this mocks what Processor does
jl.with_job_hash_context(job) do
jl.call(job, 'queue') {}
@ -34,7 +34,7 @@ class TestJobLogger < Minitest::Test
assert a
assert b
expected = /pid=#{$$} tid=#{p.tid} class=FooWorker jid=1234abc/
expected = /pid=#{$$} tid=#{p.tid} class=FooWorker jid=1234abc tags=bar,baz/
assert_match(expected, a)
assert_match(expected, b)
assert_match(/#{Time.now.utc.to_date}.+Z pid=#{$$} tid=#{p.tid} .+INFO: done/, b)
@ -44,7 +44,7 @@ class TestJobLogger < Minitest::Test
# json
@logger.formatter = Sidekiq::Logger::Formatters::JSON.new
jl = Sidekiq::JobLogger.new(@logger)
job = {"jid"=>"1234abc", "wrapped"=>"Wrapper", "class"=>"FooWorker", "bid"=>"b-xyz"}
job = {"jid"=>"1234abc", "wrapped"=>"Wrapper", "class"=>"FooWorker", "bid"=>"b-xyz", "tags" => ["bar", "baz"]}
# this mocks what Processor does
jl.with_job_hash_context(job) do
jl.call(job, 'queue') {}
@ -56,7 +56,7 @@ class TestJobLogger < Minitest::Test
keys = hsh.keys.sort
assert_equal(["ctx", "lvl", "msg", "pid", "tid", "ts"], keys)
keys = hsh["ctx"].keys.sort
assert_equal(["bid", "class", "jid"], keys)
assert_equal(["bid", "class", "jid", "tags"], keys)
end
def reset(io)

View File

@ -279,6 +279,13 @@ describe Sidekiq::Web do
assert_match(/HardWorker/, last_response.body)
end
it 'can display a single scheduled job tags' do
params = add_scheduled
get "/scheduled/#{job_params(*params)}"
assert_match(/tag1/, last_response.body)
assert_match(/tag2/, last_response.body)
end
it 'handles missing scheduled job' do
get "/scheduled/0-shouldntexist"
assert_equal 302, last_response.status
@ -579,7 +586,8 @@ describe Sidekiq::Web do
score = Time.now.to_f
msg = { 'class' => 'HardWorker',
'args' => ['bob', 1, Time.now.to_f],
'jid' => SecureRandom.hex(12) }
'jid' => SecureRandom.hex(12),
'tags' => ['tag1', 'tag2'], }
Sidekiq.redis do |conn|
conn.zadd('schedule', score, Sidekiq.dump_json(msg))
end

View File

@ -14,7 +14,12 @@
<tr>
<th><%= t('Job') %></th>
<td>
<code><%= job.display_class %></code>
<code>
<%= job.display_class %>
<% job.tags.each do |tag| %>
<span class="label label-info"><%= tag %></span>
<% end %>
</code>
</td>
</tr>
<tr>

View File

@ -87,7 +87,12 @@
<td>
<a href="<%= root_path %>queues/<%= msg['queue'] %>"><%= msg['queue'] %></a>
</td>
<td><%= job.display_class %></td>
<td>
<%= job.display_class %>
<% job.tags.each do |tag| %>
<span class="label label-info"><%= tag %></span>
<% end %>
</td>
<td>
<div class="args"><%= display_args(job.display_args) %></div>
</td>

View File

@ -42,7 +42,12 @@
<td>
<a href="<%= root_path %>queues/<%= entry.queue %>"><%= entry.queue %></a>
</td>
<td><%= entry.display_class %></td>
<td>
<%= entry.display_class %>
<% entry.tags.each do |tag| %>
<span class="label label-info"><%= tag %></span>
<% end %>
</td>
<td>
<div class="args"><%= display_args(entry.display_args) %></div>
</td>

View File

@ -27,7 +27,12 @@
<% else %>
<td><%= @total_size - (@count * (@current_page - 1) + index) %></td>
<% end %>
<td><%= h(msg.display_class) %></td>
<td>
<%= h(msg.display_class) %>
<% msg.tags.each do |tag| %>
<span class="label label-info"><%= tag %></span>
<% end %>
</td>
<td>
<% a = msg.display_args %>
<% if a.inspect.size > 100 %>

View File

@ -38,7 +38,12 @@
<td>
<a href="<%= root_path %>queues/<%= entry.queue %>"><%= entry.queue %></a>
</td>
<td><%= entry.display_class %></td>
<td>
<%= entry.display_class %>
<% entry.tags.each do |tag| %>
<span class="label label-info"><%= tag %></span>
<% end %>
</td>
<td>
<div class="args"><%= display_args(entry.display_args) %></div>
</td>