sidekiq 6.2.2 → 8.1.5

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.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +726 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +70 -39
  5. data/bin/kiq +17 -0
  6. data/bin/lint-herb +13 -0
  7. data/bin/multi_queue_bench +271 -0
  8. data/bin/sidekiq +4 -9
  9. data/bin/sidekiqload +214 -115
  10. data/bin/sidekiqmon +4 -1
  11. data/bin/webload +69 -0
  12. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
  15. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  16. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  17. data/lib/sidekiq/api.rb +729 -264
  18. data/lib/sidekiq/capsule.rb +135 -0
  19. data/lib/sidekiq/cli.rb +124 -100
  20. data/lib/sidekiq/client.rb +153 -106
  21. data/lib/sidekiq/component.rb +132 -0
  22. data/lib/sidekiq/config.rb +320 -0
  23. data/lib/sidekiq/deploy.rb +64 -0
  24. data/lib/sidekiq/embedded.rb +64 -0
  25. data/lib/sidekiq/fetch.rb +27 -26
  26. data/lib/sidekiq/iterable_job.rb +56 -0
  27. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/lib/sidekiq/job/iterable.rb +322 -0
  32. data/lib/sidekiq/job.rb +397 -5
  33. data/lib/sidekiq/job_logger.rb +23 -32
  34. data/lib/sidekiq/job_retry.rb +141 -68
  35. data/lib/sidekiq/job_util.rb +113 -0
  36. data/lib/sidekiq/launcher.rb +122 -98
  37. data/lib/sidekiq/loader.rb +57 -0
  38. data/lib/sidekiq/logger.rb +27 -106
  39. data/lib/sidekiq/manager.rb +41 -43
  40. data/lib/sidekiq/metrics/query.rb +184 -0
  41. data/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/lib/sidekiq/middleware/chain.rb +96 -51
  44. data/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/lib/sidekiq/middleware/i18n.rb +8 -4
  46. data/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/lib/sidekiq/monitor.rb +16 -6
  48. data/lib/sidekiq/paginator.rb +37 -10
  49. data/lib/sidekiq/processor.rb +105 -87
  50. data/lib/sidekiq/profiler.rb +73 -0
  51. data/lib/sidekiq/rails.rb +49 -36
  52. data/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/lib/sidekiq/redis_connection.rb +55 -86
  54. data/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/lib/sidekiq/scheduled.rb +106 -50
  56. data/lib/sidekiq/systemd.rb +2 -0
  57. data/lib/sidekiq/test_api.rb +331 -0
  58. data/lib/sidekiq/testing/inline.rb +2 -30
  59. data/lib/sidekiq/testing.rb +2 -342
  60. data/lib/sidekiq/transaction_aware_client.rb +59 -0
  61. data/lib/sidekiq/tui/controls.rb +53 -0
  62. data/lib/sidekiq/tui/filtering.rb +53 -0
  63. data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  64. data/lib/sidekiq/tui/tabs/busy.rb +118 -0
  65. data/lib/sidekiq/tui/tabs/dead.rb +19 -0
  66. data/lib/sidekiq/tui/tabs/home.rb +144 -0
  67. data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  68. data/lib/sidekiq/tui/tabs/queues.rb +95 -0
  69. data/lib/sidekiq/tui/tabs/retries.rb +19 -0
  70. data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  71. data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  72. data/lib/sidekiq/tui/tabs.rb +15 -0
  73. data/lib/sidekiq/tui.rb +382 -0
  74. data/lib/sidekiq/version.rb +6 -1
  75. data/lib/sidekiq/web/action.rb +149 -64
  76. data/lib/sidekiq/web/application.rb +376 -268
  77. data/lib/sidekiq/web/config.rb +117 -0
  78. data/lib/sidekiq/web/helpers.rb +213 -87
  79. data/lib/sidekiq/web/router.rb +61 -74
  80. data/lib/sidekiq/web.rb +71 -100
  81. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  82. data/lib/sidekiq.rb +95 -196
  83. data/sidekiq.gemspec +14 -11
  84. data/web/assets/images/logo.png +0 -0
  85. data/web/assets/images/status.png +0 -0
  86. data/web/assets/javascripts/application.js +171 -57
  87. data/web/assets/javascripts/base-charts.js +120 -0
  88. data/web/assets/javascripts/chart.min.js +13 -0
  89. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  90. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  91. data/web/assets/javascripts/dashboard-charts.js +194 -0
  92. data/web/assets/javascripts/dashboard.js +41 -274
  93. data/web/assets/javascripts/metrics.js +280 -0
  94. data/web/assets/stylesheets/style.css +776 -0
  95. data/web/locales/ar.yml +72 -70
  96. data/web/locales/cs.yml +64 -62
  97. data/web/locales/da.yml +62 -53
  98. data/web/locales/de.yml +67 -65
  99. data/web/locales/el.yml +45 -24
  100. data/web/locales/en.yml +93 -69
  101. data/web/locales/es.yml +91 -68
  102. data/web/locales/fa.yml +67 -65
  103. data/web/locales/fr.yml +82 -67
  104. data/web/locales/gd.yml +110 -0
  105. data/web/locales/he.yml +67 -64
  106. data/web/locales/hi.yml +61 -59
  107. data/web/locales/it.yml +94 -54
  108. data/web/locales/ja.yml +74 -68
  109. data/web/locales/ko.yml +54 -52
  110. data/web/locales/lt.yml +68 -66
  111. data/web/locales/nb.yml +63 -61
  112. data/web/locales/nl.yml +54 -52
  113. data/web/locales/pl.yml +47 -45
  114. data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
  115. data/web/locales/pt.yml +53 -51
  116. data/web/locales/ru.yml +69 -66
  117. data/web/locales/sv.yml +55 -53
  118. data/web/locales/ta.yml +62 -60
  119. data/web/locales/tr.yml +102 -0
  120. data/web/locales/uk.yml +87 -61
  121. data/web/locales/ur.yml +66 -64
  122. data/web/locales/vi.yml +69 -67
  123. data/web/locales/zh-CN.yml +107 -0
  124. data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
  125. data/web/views/_footer.html.erb +32 -0
  126. data/web/views/_job_info.html.erb +115 -0
  127. data/web/views/_metrics_period_select.html.erb +15 -0
  128. data/web/views/_nav.html.erb +45 -0
  129. data/web/views/_paging.html.erb +26 -0
  130. data/web/views/_poll_link.html.erb +4 -0
  131. data/web/views/_summary.html.erb +40 -0
  132. data/web/views/busy.html.erb +151 -0
  133. data/web/views/dashboard.html.erb +104 -0
  134. data/web/views/dead.html.erb +38 -0
  135. data/web/views/filtering.html.erb +6 -0
  136. data/web/views/layout.html.erb +26 -0
  137. data/web/views/metrics.html.erb +85 -0
  138. data/web/views/metrics_for_job.html.erb +58 -0
  139. data/web/views/morgue.html.erb +69 -0
  140. data/web/views/profiles.html.erb +43 -0
  141. data/web/views/queue.html.erb +57 -0
  142. data/web/views/queues.html.erb +46 -0
  143. data/web/views/retries.html.erb +77 -0
  144. data/web/views/retry.html.erb +39 -0
  145. data/web/views/scheduled.html.erb +64 -0
  146. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  147. metadata +130 -61
  148. data/LICENSE +0 -9
  149. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  150. data/lib/sidekiq/delay.rb +0 -41
  151. data/lib/sidekiq/exception_handler.rb +0 -27
  152. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  153. data/lib/sidekiq/extensions/active_record.rb +0 -43
  154. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  155. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  156. data/lib/sidekiq/util.rb +0 -95
  157. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  158. data/lib/sidekiq/worker.rb +0 -244
  159. data/web/assets/stylesheets/application-dark.css +0 -147
  160. data/web/assets/stylesheets/application-rtl.css +0 -246
  161. data/web/assets/stylesheets/application.css +0 -1053
  162. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  163. data/web/assets/stylesheets/bootstrap.css +0 -5
  164. data/web/locales/zh-cn.yml +0 -68
  165. data/web/views/_footer.erb +0 -20
  166. data/web/views/_job_info.erb +0 -89
  167. data/web/views/_nav.erb +0 -52
  168. data/web/views/_paging.erb +0 -23
  169. data/web/views/_poll_link.erb +0 -7
  170. data/web/views/_status.erb +0 -4
  171. data/web/views/_summary.erb +0 -40
  172. data/web/views/busy.erb +0 -132
  173. data/web/views/dashboard.erb +0 -83
  174. data/web/views/dead.erb +0 -34
  175. data/web/views/layout.erb +0 -42
  176. data/web/views/morgue.erb +0 -78
  177. data/web/views/queue.erb +0 -55
  178. data/web/views/queues.erb +0 -38
  179. data/web/views/retries.erb +0 -83
  180. data/web/views/retry.erb +0 -34
  181. data/web/views/scheduled.erb +0 -57
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/manager"
4
- require "sidekiq/fetch"
4
+ require "sidekiq/capsule"
5
5
  require "sidekiq/scheduled"
6
+ require "sidekiq/ring_buffer"
6
7
 
7
8
  module Sidekiq
8
- # The Launcher starts the Manager and Poller threads and provides the process heartbeat.
9
+ # The Launcher starts the Capsule Managers, the Poller thread and provides the process heartbeat.
9
10
  class Launcher
10
- include Util
11
+ include Sidekiq::Component
11
12
 
12
13
  STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
13
14
 
@@ -15,141 +16,153 @@ module Sidekiq
15
16
  proc { "sidekiq" },
16
17
  proc { Sidekiq::VERSION },
17
18
  proc { |me, data| data["tag"] },
18
- proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data["concurrency"]} busy]" },
19
+ proc { |me, data| "[#{Processor::WORK_STATE.size} of #{me.config.total_concurrency} busy]" },
19
20
  proc { |me, data| "stopping" if me.stopping? }
20
21
  ]
21
22
 
22
- attr_accessor :manager, :poller, :fetcher
23
+ attr_accessor :managers, :poller
23
24
 
24
- def initialize(options)
25
- options[:fetch] ||= BasicFetch.new(options)
26
- @manager = Sidekiq::Manager.new(options)
27
- @poller = Sidekiq::Scheduled::Poller.new
25
+ def initialize(config, embedded: false)
26
+ @config = config
27
+ @embedded = embedded
28
+ @managers = config.capsules.values.map do |cap|
29
+ Sidekiq::Manager.new(cap)
30
+ end
31
+ @poller = Sidekiq::Scheduled::Poller.new(@config)
28
32
  @done = false
29
- @options = options
30
33
  end
31
34
 
32
- def run
33
- @thread = safe_thread("heartbeat", &method(:start_heartbeat))
35
+ # Start this Sidekiq instance. If an embedding process already
36
+ # has a heartbeat thread, caller can use `async_beat: false`
37
+ # and instead have thread call Launcher#heartbeat every N seconds.
38
+ def run(async_beat: true)
39
+ logger.debug { @config.merge!({}) }
40
+ Sidekiq.freeze!
41
+ @thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
34
42
  @poller.start
35
- @manager.start
43
+ @managers.each(&:start)
36
44
  end
37
45
 
38
46
  # Stops this instance from processing any more jobs,
39
- #
40
47
  def quiet
48
+ return if @done
49
+
41
50
  @done = true
42
- @manager.quiet
51
+ @managers.each(&:quiet)
43
52
  @poller.terminate
53
+ fire_event(:quiet, reverse: true)
44
54
  end
45
55
 
46
- # Shuts down the process. This method does not
47
- # return until all work is complete and cleaned up.
48
- # It can take up to the timeout to complete.
56
+ # Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
49
57
  def stop
50
- deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
51
-
52
- @done = true
53
- @manager.quiet
54
- @poller.terminate
58
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
55
59
 
56
- @manager.stop(deadline)
60
+ quiet
61
+ stoppers = @managers.map do |mgr|
62
+ Thread.new do
63
+ mgr.stop(deadline)
64
+ end
65
+ end
57
66
 
58
- # Requeue everything in case there was a worker who grabbed work while stopped
59
- # This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
60
- strategy = @options[:fetch]
61
- strategy.bulk_requeue([], @options)
67
+ fire_event(:shutdown, reverse: true)
68
+ stoppers.each(&:join)
62
69
 
63
70
  clear_heartbeat
71
+ fire_event(:exit, reverse: true)
64
72
  end
65
73
 
66
74
  def stopping?
67
75
  @done
68
76
  end
69
77
 
70
- private unless $TESTING
78
+ # If embedding Sidekiq, you can have the process heartbeat
79
+ # call this method to regularly heartbeat rather than creating
80
+ # a separate thread.
81
+ def heartbeat
82
+
83
+ end
84
+
85
+ private
86
+
87
+ BEAT_PAUSE = 10
71
88
 
72
89
  def start_heartbeat
73
90
  loop do
74
- heartbeat
75
- sleep 5
91
+ beat
92
+ sleep BEAT_PAUSE
76
93
  end
77
- Sidekiq.logger.info("Heartbeat stopping...")
94
+ logger.info("Heartbeat stopping...")
95
+ end
96
+
97
+ def beat
98
+ $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
99
+
78
100
  end
79
101
 
80
102
  def clear_heartbeat
103
+ flush_stats
104
+
81
105
  # Remove record from Redis since we are shutting down.
82
106
  # Note we don't stop the heartbeat thread; if the process
83
107
  # doesn't actually exit, it'll reappear in the Web UI.
84
- Sidekiq.redis do |conn|
85
- conn.pipelined do
86
- conn.srem("processes", identity)
87
- conn.unlink("#{identity}:workers")
108
+ redis do |conn|
109
+ conn.pipelined do |pipeline|
110
+ pipeline.srem("processes", [identity])
111
+ pipeline.unlink("#{identity}:work")
88
112
  end
89
113
  end
90
114
  rescue
91
115
  # best effort, ignore network errors
92
116
  end
93
117
 
94
- def heartbeat
95
- $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ")
96
-
97
-
98
- end
99
-
100
- def self.flush_stats
118
+ def flush_stats
101
119
  fails = Processor::FAILURE.reset
102
120
  procd = Processor::PROCESSED.reset
103
121
  return if fails + procd == 0
104
122
 
105
123
  nowdate = Time.now.utc.strftime("%Y-%m-%d")
106
124
  begin
107
- Sidekiq.redis do |conn|
108
- conn.pipelined do
109
- conn.incrby("stat:processed", procd)
110
- conn.incrby("stat:processed:#{nowdate}", procd)
111
- conn.expire("stat:processed:#{nowdate}", STATS_TTL)
112
-
113
- conn.incrby("stat:failed", fails)
114
- conn.incrby("stat:failed:#{nowdate}", fails)
115
- conn.expire("stat:failed:#{nowdate}", STATS_TTL)
125
+ redis do |conn|
126
+ conn.pipelined do |pipeline|
127
+ pipeline.incrby("stat:processed", procd)
128
+ pipeline.incrby("stat:processed:#{nowdate}", procd)
129
+ pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
130
+
131
+ pipeline.incrby("stat:failed", fails)
132
+ pipeline.incrby("stat:failed:#{nowdate}", fails)
133
+ pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
116
134
  end
117
135
  end
118
136
  rescue => ex
119
- # we're exiting the process, things might be shut down so don't
120
- # try to handle the exception
121
- Sidekiq.logger.warn("Unable to flush stats: #{ex}")
137
+ logger.warn("Unable to flush stats: #{ex}")
122
138
  end
123
139
  end
124
- at_exit(&method(:flush_stats))
125
140
 
126
141
  def ❤
127
142
  key = identity
128
143
  fails = procd = 0
129
144
 
145
+ idle_timeout = config[:redis_idle_timeout]
146
+ if idle_timeout
147
+ config.capsules.each_value { |cap| cap.local_redis_pool.reap(idle_seconds: idle_timeout, &:close) }
148
+ config.local_redis_pool.reap(idle_seconds: idle_timeout, &:close)
149
+ end
150
+
130
151
  begin
131
- fails = Processor::FAILURE.reset
132
- procd = Processor::PROCESSED.reset
133
- curstate = Processor::WORKER_STATE.dup
134
-
135
- workers_key = "#{key}:workers"
136
- nowdate = Time.now.utc.strftime("%Y-%m-%d")
137
-
138
- Sidekiq.redis do |conn|
139
- conn.multi do
140
- conn.incrby("stat:processed", procd)
141
- conn.incrby("stat:processed:#{nowdate}", procd)
142
- conn.expire("stat:processed:#{nowdate}", STATS_TTL)
143
-
144
- conn.incrby("stat:failed", fails)
145
- conn.incrby("stat:failed:#{nowdate}", fails)
146
- conn.expire("stat:failed:#{nowdate}", STATS_TTL)
147
-
148
- conn.unlink(workers_key)
149
- curstate.each_pair do |tid, hash|
150
- conn.hset(workers_key, tid, Sidekiq.dump_json(hash))
152
+ flush_stats
153
+
154
+ curstate = Processor::WORK_STATE.dup
155
+ curstate.transform_values! { |val| Sidekiq.dump_json(val) }
156
+
157
+ redis do |conn|
158
+ # work is the current set of executing jobs
159
+ work_key = "#{key}:work"
160
+ conn.multi do |transaction|
161
+ transaction.unlink(work_key)
162
+ if curstate.size > 0
163
+ transaction.hset(work_key, curstate)
164
+ transaction.expire(work_key, 60)
151
165
  end
152
- conn.expire(workers_key, 60)
153
166
  end
154
167
  end
155
168
 
@@ -158,27 +171,27 @@ module Sidekiq
158
171
  fails = procd = 0
159
172
  kb = memory_usage(::Process.pid)
160
173
 
161
- _, exists, _, _, msg = Sidekiq.redis { |conn|
162
- conn.multi {
163
- conn.sadd("processes", key)
164
- conn.exists?(key)
165
- conn.hmset(key, "info", to_json,
174
+ _, exists, _, _, signal = redis { |conn|
175
+ conn.multi { |transaction|
176
+ transaction.sadd("processes", [key])
177
+ transaction.exists(key)
178
+ transaction.hset(key, "info", to_json,
179
+ "concurrency", @config.total_concurrency,
166
180
  "busy", curstate.size,
167
181
  "beat", Time.now.to_f,
168
182
  "rtt_us", rtt,
169
- "quiet", @done,
183
+ "quiet", @done.to_s,
170
184
  "rss", kb)
171
- conn.expire(key, 60)
172
- conn.rpop("#{key}-signals")
185
+ transaction.expire(key, 60)
186
+ transaction.rpop("#{key}-signals")
173
187
  }
174
188
  }
175
189
 
176
190
  # first heartbeat or recovering from an outage and need to reestablish our heartbeat
177
- fire_event(:heartbeat) unless exists
178
-
179
- return unless msg
191
+ fire_event(:heartbeat) unless exists > 0
192
+ fire_event(:beat, oneshot: false)
180
193
 
181
- ::Process.kill(msg, ::Process.pid)
194
+ ::Process.kill(signal, ::Process.pid) if signal && !@embedded
182
195
  rescue => e
183
196
  # ignore all redis/network issues
184
197
  logger.error("heartbeat: #{e}")
@@ -196,7 +209,7 @@ module Sidekiq
196
209
 
197
210
  def check_rtt
198
211
  a = b = 0
199
- Sidekiq.redis do |x|
212
+ redis do |x|
200
213
  a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
201
214
  x.ping
202
215
  b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
@@ -207,10 +220,12 @@ module Sidekiq
207
220
  # Workable is < 10,000µs
208
221
  # Log a warning if it's a disaster.
209
222
  if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
210
- Sidekiq.logger.warn <<~EOM
211
- Your Redis network connection is performing extremely poorly.
223
+ logger.warn <<~EOM
224
+ Your Redis network connection appears to be performing poorly.
212
225
  Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
213
- Ensure Redis is running in the same AZ or datacenter as Sidekiq.
226
+ Ensure Redis is running in the same AZ or datacenter as Sidekiq and that
227
+ your Sidekiq process is not CPU-saturated; reduce your concurrency and/or
228
+ see https://github.com/sidekiq/sidekiq/discussions/5039
214
229
  EOM
215
230
  RTT_READINGS.reset
216
231
  end
@@ -242,11 +257,20 @@ module Sidekiq
242
257
  "hostname" => hostname,
243
258
  "started_at" => Time.now.to_f,
244
259
  "pid" => ::Process.pid,
245
- "tag" => @options[:tag] || "",
246
- "concurrency" => @options[:concurrency],
247
- "queues" => @options[:queues].uniq,
248
- "labels" => @options[:labels],
249
- "identity" => identity
260
+ "tag" => @config[:tag] || "",
261
+ "capsules" => @config.capsules.each_with_object({}) { |(name, cap), memo|
262
+ memo[name] = cap.to_h
263
+ },
264
+ #####
265
+ # TODO deprecated, remove in 9.0
266
+ # This data is now found in the `capsules` element above
267
+ "queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
268
+ "weights" => @config.capsules.values.map(&:weights),
269
+ #####
270
+ "labels" => @config[:labels].to_a,
271
+ "identity" => identity,
272
+ "version" => Sidekiq::VERSION,
273
+ "embedded" => @embedded
250
274
  }
251
275
  end
252
276
 
@@ -0,0 +1,57 @@
1
+ module Sidekiq
2
+ require "sidekiq/component"
3
+
4
+ class Loader
5
+ include Sidekiq::Component
6
+
7
+ def initialize(cfg = Sidekiq.default_configuration)
8
+ @config = cfg
9
+ @load_hooks = Hash.new { |h, k| h[k] = [] }
10
+ @loaded = Set.new
11
+ @lock = Mutex.new
12
+ end
13
+
14
+ # Declares a block that will be executed when a Sidekiq component is fully
15
+ # loaded. If the component has already loaded, the block is executed
16
+ # immediately.
17
+ #
18
+ # Sidekiq.loader.on_load(:api) do
19
+ # # extend the sidekiq API
20
+ # end
21
+ #
22
+ def on_load(name, &block)
23
+ # we don't want to hold the lock while calling the block
24
+ to_run = nil
25
+
26
+ @lock.synchronize do
27
+ if @loaded.include?(name)
28
+ to_run = block
29
+ else
30
+ @load_hooks[name] << block
31
+ end
32
+ end
33
+
34
+ to_run&.call
35
+ nil
36
+ end
37
+
38
+ # Executes all blocks registered to +name+ via on_load.
39
+ #
40
+ # Sidekiq.loader.run_load_hooks(:api)
41
+ #
42
+ # In the case of the above example, it will execute all hooks registered for +:api+.
43
+ #
44
+ def run_load_hooks(name)
45
+ hks = @lock.synchronize do
46
+ @loaded << name
47
+ @load_hooks.delete(name)
48
+ end
49
+
50
+ hks&.each do |blk|
51
+ blk.call
52
+ rescue => ex
53
+ handle_exception(ex, hook: name)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -16,133 +16,54 @@ module Sidekiq
16
16
  def self.current
17
17
  Thread.current[:sidekiq_context] ||= {}
18
18
  end
19
- end
20
-
21
- module LoggingUtils
22
- LEVELS = {
23
- "debug" => 0,
24
- "info" => 1,
25
- "warn" => 2,
26
- "error" => 3,
27
- "fatal" => 4
28
- }
29
- LEVELS.default_proc = proc do |_, level|
30
- Sidekiq.logger.warn("Invalid log level: #{level.inspect}")
31
- nil
32
- end
33
-
34
- def debug?
35
- level <= 0
36
- end
37
19
 
38
- def info?
39
- level <= 1
40
- end
41
-
42
- def warn?
43
- level <= 2
44
- end
45
-
46
- def error?
47
- level <= 3
48
- end
49
-
50
- def fatal?
51
- level <= 4
52
- end
53
-
54
- def local_level
55
- Thread.current[:sidekiq_log_level]
56
- end
57
-
58
- def local_level=(level)
59
- case level
60
- when Integer
61
- Thread.current[:sidekiq_log_level] = level
62
- when Symbol, String
63
- Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
64
- when nil
65
- Thread.current[:sidekiq_log_level] = nil
66
- else
67
- raise ArgumentError, "Invalid log level: #{level.inspect}"
68
- end
69
- end
70
-
71
- def level
72
- local_level || super
73
- end
74
-
75
- # Change the thread-local level for the duration of the given block.
76
- def log_at(level)
77
- old_local_level = local_level
78
- self.local_level = level
79
- yield
80
- ensure
81
- self.local_level = old_local_level
82
- end
83
-
84
- # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
85
- # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
86
- def add(severity, message = nil, progname = nil, &block)
87
- severity ||= ::Logger::UNKNOWN
88
- progname ||= @progname
89
-
90
- return true if @logdev.nil? || severity < level
91
-
92
- if message.nil?
93
- if block
94
- message = yield
95
- else
96
- message = progname
97
- progname = @progname
98
- end
99
- end
100
-
101
- @logdev.write format_message(format_severity(severity), Time.now, progname, message)
20
+ def self.add(k, v)
21
+ current[k] = v
102
22
  end
103
23
  end
104
24
 
105
25
  class Logger < ::Logger
106
- include LoggingUtils
107
-
108
- def initialize(*args, **kwargs)
109
- super
110
- self.formatter = Sidekiq.log_formatter
111
- end
112
-
113
26
  module Formatters
114
27
  class Base < ::Logger::Formatter
28
+ COLORS = {
29
+ "DEBUG" => "\e[1;32mDEBUG\e[0m", # green
30
+ "INFO" => "\e[1;34mINFO \e[0m", # blue
31
+ "WARN" => "\e[1;33mWARN \e[0m", # yellow
32
+ "ERROR" => "\e[1;31mERROR\e[0m", # red
33
+ "FATAL" => "\e[1;35mFATAL\e[0m" # pink
34
+ }
35
+
115
36
  def tid
116
37
  Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
117
38
  end
118
39
 
119
- def ctx
120
- Sidekiq::Context.current
40
+ def format_context(ctxt = Sidekiq::Context.current)
41
+ (ctxt.size == 0) ? "" : " #{ctxt.map { |k, v|
42
+ case v
43
+ when Array
44
+ "#{k}=#{v.join(",")}"
45
+ else
46
+ "#{k}=#{v}"
47
+ end
48
+ }.join(" ")}"
121
49
  end
50
+ end
122
51
 
123
- def format_context
124
- if ctx.any?
125
- " " + ctx.compact.map { |k, v|
126
- case v
127
- when Array
128
- "#{k}=#{v.join(",")}"
129
- else
130
- "#{k}=#{v}"
131
- end
132
- }.join(" ")
133
- end
52
+ class Pretty < Base
53
+ def call(severity, time, program_name, message)
54
+ "#{COLORS[severity]} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
134
55
  end
135
56
  end
136
57
 
137
- class Pretty < Base
58
+ class Plain < Base
138
59
  def call(severity, time, program_name, message)
139
- "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
60
+ "#{severity} #{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
140
61
  end
141
62
  end
142
63
 
143
64
  class WithoutTimestamp < Pretty
144
65
  def call(severity, time, program_name, message)
145
- "pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
66
+ "#{COLORS[severity]} pid=#{::Process.pid} tid=#{tid}#{format_context}: #{message}\n"
146
67
  end
147
68
  end
148
69
 
@@ -155,7 +76,7 @@ module Sidekiq
155
76
  lvl: severity,
156
77
  msg: message
157
78
  }
158
- c = ctx
79
+ c = Sidekiq::Context.current
159
80
  hash["ctx"] = c unless c.empty?
160
81
 
161
82
  Sidekiq.dump_json(hash) << "\n"