good_job 1.0.3 → 1.1.4
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/CHANGELOG.md +76 -11
- data/README.md +174 -32
- data/exe/good_job +1 -0
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -12
- data/lib/good_job.rb +28 -2
- data/lib/good_job/adapter.rb +33 -15
- data/lib/good_job/cli.rb +27 -43
- data/lib/good_job/configuration.rb +55 -0
- data/lib/good_job/current_execution.rb +35 -0
- data/lib/good_job/job.rb +40 -5
- data/lib/good_job/log_subscriber.rb +167 -0
- data/lib/good_job/multi_scheduler.rb +34 -0
- data/lib/good_job/notifier.rb +116 -0
- data/lib/good_job/performer.rb +11 -1
- data/lib/good_job/railtie.rb +11 -0
- data/lib/good_job/scheduler.rb +141 -26
- data/lib/good_job/version.rb +1 -1
- metadata +81 -7
- data/lib/good_job/logging.rb +0 -70
data/exe/good_job
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
module ActiveJob
|
2
2
|
module QueueAdapters
|
3
3
|
class GoodJobAdapter < GoodJob::Adapter
|
4
|
-
def initialize(execution_mode: nil)
|
5
|
-
|
6
|
-
|
7
|
-
elsif ENV['GOOD_JOB_EXECUTION_MODE'].present?
|
8
|
-
ENV['GOOD_JOB_EXECUTION_MODE'].to_sym
|
9
|
-
elsif Rails.env.development? || Rails.env.test?
|
10
|
-
:inline
|
11
|
-
else
|
12
|
-
:external
|
13
|
-
end
|
14
|
-
|
15
|
-
super(execution_mode: execution_mode)
|
4
|
+
def initialize(execution_mode: nil, max_threads: nil, poll_interval: nil, scheduler: nil, inline: false)
|
5
|
+
configuration = GoodJob::Configuration.new({ execution_mode: execution_mode }, env: ENV)
|
6
|
+
super(execution_mode: configuration.rails_execution_mode, max_threads: max_threads, poll_interval: poll_interval, scheduler: scheduler, inline: inline)
|
16
7
|
end
|
17
8
|
end
|
18
9
|
end
|
data/lib/good_job.rb
CHANGED
@@ -1,20 +1,46 @@
|
|
1
1
|
require "rails"
|
2
2
|
require 'good_job/railtie'
|
3
3
|
|
4
|
-
require 'good_job/
|
4
|
+
require 'good_job/configuration'
|
5
|
+
require 'good_job/log_subscriber'
|
5
6
|
require 'good_job/lockable'
|
6
7
|
require 'good_job/job'
|
7
8
|
require 'good_job/scheduler'
|
9
|
+
require 'good_job/multi_scheduler'
|
8
10
|
require 'good_job/adapter'
|
9
11
|
require 'good_job/pg_locks'
|
10
12
|
require 'good_job/performer'
|
13
|
+
require 'good_job/current_execution'
|
14
|
+
require 'good_job/notifier'
|
11
15
|
|
12
16
|
require 'active_job/queue_adapters/good_job_adapter'
|
13
17
|
|
14
18
|
module GoodJob
|
19
|
+
mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
15
20
|
mattr_accessor :preserve_job_records, default: false
|
16
21
|
mattr_accessor :reperform_jobs_on_standard_error, default: true
|
17
|
-
|
22
|
+
mattr_accessor :on_thread_error, default: nil
|
18
23
|
|
19
24
|
ActiveSupport.run_load_hooks(:good_job, self)
|
25
|
+
|
26
|
+
# Shuts down all execution pools
|
27
|
+
# @param wait [Boolean] whether to wait for shutdown
|
28
|
+
# @return [void]
|
29
|
+
def self.shutdown(wait: true)
|
30
|
+
Notifier.instances.each { |adapter| adapter.shutdown(wait: wait) }
|
31
|
+
Scheduler.instances.each { |scheduler| scheduler.shutdown(wait: wait) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Tests if execution pools are shut down
|
35
|
+
# @return [Boolean] whether execution pools are shut down
|
36
|
+
def self.shutdown?
|
37
|
+
Notifier.instances.all?(&:shutdown?) && Scheduler.instances.all?(&:shutdown?)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Restarts all execution pools
|
41
|
+
# @return [void]
|
42
|
+
def self.restart
|
43
|
+
Notifier.instances.each(&:restart)
|
44
|
+
Scheduler.instances.each(&:restart)
|
45
|
+
end
|
20
46
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
module GoodJob
|
2
2
|
class Adapter
|
3
|
-
EXECUTION_MODES = [:
|
3
|
+
EXECUTION_MODES = [:async, :external, :inline].freeze
|
4
4
|
|
5
|
-
def initialize(execution_mode: nil, inline: false)
|
6
|
-
if inline
|
5
|
+
def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil, scheduler: nil, notifier: nil, inline: false)
|
6
|
+
if inline && execution_mode.nil?
|
7
7
|
ActiveSupport::Deprecation.warn('GoodJob::Adapter#new(inline: true) is deprecated; use GoodJob::Adapter.new(execution_mode: :inline) instead')
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
execution_mode = :inline
|
9
|
+
end
|
10
|
+
|
11
|
+
configuration = GoodJob::Configuration.new(
|
12
|
+
execution_mode: execution_mode,
|
13
|
+
queues: queues,
|
14
|
+
max_threads: max_threads,
|
15
|
+
poll_interval: poll_interval
|
16
|
+
)
|
17
|
+
|
18
|
+
@execution_mode = configuration.execution_mode
|
19
|
+
raise ArgumentError, "execution_mode: must be one of #{EXECUTION_MODES.join(', ')}." unless EXECUTION_MODES.include?(@execution_mode)
|
11
20
|
|
12
|
-
|
13
|
-
|
14
|
-
@
|
21
|
+
if @execution_mode == :async # rubocop:disable Style/GuardClause
|
22
|
+
@notifier = notifier || GoodJob::Notifier.new
|
23
|
+
@scheduler = scheduler || GoodJob::Scheduler.from_configuration(configuration)
|
24
|
+
@notifier.recipients << [@scheduler, :create_thread]
|
15
25
|
end
|
16
26
|
end
|
17
27
|
|
@@ -34,11 +44,23 @@ module GoodJob
|
|
34
44
|
end
|
35
45
|
end
|
36
46
|
|
47
|
+
executed_locally = execute_async? && @scheduler.create_thread(queue_name: good_job.queue_name)
|
48
|
+
Notifier.notify(queue_name: good_job.queue_name) unless executed_locally
|
49
|
+
|
37
50
|
good_job
|
38
51
|
end
|
39
52
|
|
40
|
-
def shutdown(wait: true)
|
41
|
-
|
53
|
+
def shutdown(wait: true)
|
54
|
+
@notifier&.shutdown(wait: wait)
|
55
|
+
@scheduler&.shutdown(wait: wait)
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_async?
|
59
|
+
@execution_mode == :async
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute_externally?
|
63
|
+
@execution_mode == :external
|
42
64
|
end
|
43
65
|
|
44
66
|
def execute_inline?
|
@@ -49,9 +71,5 @@ module GoodJob
|
|
49
71
|
ActiveSupport::Deprecation.warn('GoodJob::Adapter::inline? is deprecated; use GoodJob::Adapter::execute_inline? instead')
|
50
72
|
execute_inline?
|
51
73
|
end
|
52
|
-
|
53
|
-
def execute_externally?
|
54
|
-
@execution_mode == :external
|
55
|
-
end
|
56
74
|
end
|
57
75
|
end
|
data/lib/good_job/cli.rb
CHANGED
@@ -10,47 +10,18 @@ module GoodJob
|
|
10
10
|
desc: "Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)"
|
11
11
|
method_option :queues,
|
12
12
|
type: :string,
|
13
|
-
banner: "queue1,queue2",
|
14
|
-
desc: "Queues to work from. Separate multiple queues with commas (default: *)"
|
13
|
+
banner: "queue1,queue2(;queue3,queue4:5;-queue1,queue2)",
|
14
|
+
desc: "Queues to work from. Separate multiple queues with commas; exclude queues with a leading minus; separate isolated execution pools with semicolons and threads with colons (default: *)"
|
15
15
|
method_option :poll_interval,
|
16
16
|
type: :numeric,
|
17
17
|
desc: "Interval between polls for available jobs in seconds (default: 1)"
|
18
18
|
def start
|
19
|
-
|
19
|
+
set_up_application!
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
ActiveRecord::Base.connection_pool.size
|
26
|
-
).to_i
|
27
|
-
|
28
|
-
queue_names = (
|
29
|
-
options[:queues] ||
|
30
|
-
ENV['GOOD_JOB_QUEUES'] ||
|
31
|
-
'*'
|
32
|
-
).split(',').map(&:strip)
|
33
|
-
|
34
|
-
poll_interval = (
|
35
|
-
options[:poll_interval] ||
|
36
|
-
ENV['GOOD_JOB_POLL_INTERVAL']
|
37
|
-
).to_i
|
38
|
-
|
39
|
-
job_query = GoodJob::Job.all.priority_ordered
|
40
|
-
queue_names_without_all = queue_names.reject { |q| q == '*' }
|
41
|
-
job_query = job_query.where(queue_name: queue_names_without_all) unless queue_names_without_all.size.zero?
|
42
|
-
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock)
|
43
|
-
|
44
|
-
$stdout.puts "GoodJob worker starting with max_threads=#{max_threads} on queues=#{queue_names.join(',')}"
|
45
|
-
|
46
|
-
timer_options = {}
|
47
|
-
timer_options[:execution_interval] = poll_interval if poll_interval.positive?
|
48
|
-
|
49
|
-
pool_options = {
|
50
|
-
max_threads: max_threads,
|
51
|
-
}
|
52
|
-
|
53
|
-
scheduler = GoodJob::Scheduler.new(job_performer, timer_options: timer_options, pool_options: pool_options)
|
21
|
+
notifier = GoodJob::Notifier.new
|
22
|
+
configuration = GoodJob::Configuration.new(options)
|
23
|
+
scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
24
|
+
notifier.recipients << [scheduler, :create_thread]
|
54
25
|
|
55
26
|
@stop_good_job_executable = false
|
56
27
|
%w[INT TERM].each do |signal|
|
@@ -59,27 +30,40 @@ module GoodJob
|
|
59
30
|
|
60
31
|
Kernel.loop do
|
61
32
|
sleep 0.1
|
62
|
-
break if @stop_good_job_executable || scheduler.shutdown?
|
33
|
+
break if @stop_good_job_executable || scheduler.shutdown? || notifier.shutdown?
|
63
34
|
end
|
64
35
|
|
65
|
-
|
36
|
+
notifier.shutdown
|
66
37
|
scheduler.shutdown
|
67
|
-
$stdout.puts "GoodJob's jobs finished, exiting..."
|
68
38
|
end
|
69
39
|
|
40
|
+
default_task :start
|
41
|
+
|
70
42
|
desc :cleanup_preserved_jobs, "Delete preserved job records"
|
71
43
|
method_option :before_seconds_ago,
|
72
44
|
type: :numeric,
|
73
45
|
default: 24 * 60 * 60,
|
74
46
|
desc: "Delete records finished more than this many seconds ago"
|
47
|
+
|
75
48
|
def cleanup_preserved_jobs
|
76
|
-
|
49
|
+
set_up_application!
|
77
50
|
|
78
51
|
timestamp = Time.current - options[:before_seconds_ago]
|
79
|
-
|
80
|
-
|
52
|
+
ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { before_seconds_ago: options[:before_seconds_ago], timestamp: timestamp }) do |payload|
|
53
|
+
deleted_records_count = GoodJob::Job.finished(timestamp).delete_all
|
54
|
+
|
55
|
+
payload[:deleted_records_count] = deleted_records_count
|
56
|
+
end
|
81
57
|
end
|
82
58
|
|
83
|
-
|
59
|
+
no_commands do
|
60
|
+
def set_up_application!
|
61
|
+
require RAILS_ENVIRONMENT_RB
|
62
|
+
return unless defined?(GOOD_JOB_LOG_TO_STDOUT) && GOOD_JOB_LOG_TO_STDOUT && !ActiveSupport::Logger.logger_outputs_to?(GoodJob.logger, STDOUT)
|
63
|
+
|
64
|
+
GoodJob::LogSubscriber.loggers << ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
65
|
+
GoodJob::LogSubscriber.reset_logger
|
66
|
+
end
|
67
|
+
end
|
84
68
|
end
|
85
69
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module GoodJob
|
2
|
+
class Configuration
|
3
|
+
attr_reader :options, :env
|
4
|
+
|
5
|
+
def initialize(options, env: ENV)
|
6
|
+
@options = options
|
7
|
+
@env = env
|
8
|
+
end
|
9
|
+
|
10
|
+
def execution_mode(default: :external)
|
11
|
+
if options[:execution_mode]
|
12
|
+
options[:execution_mode]
|
13
|
+
elsif env['GOOD_JOB_EXECUTION_MODE'].present?
|
14
|
+
env['GOOD_JOB_EXECUTION_MODE'].to_sym
|
15
|
+
else
|
16
|
+
default
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def rails_execution_mode
|
21
|
+
if execution_mode(default: nil)
|
22
|
+
execution_mode
|
23
|
+
elsif Rails.env.development?
|
24
|
+
:inline
|
25
|
+
elsif Rails.env.test?
|
26
|
+
:inline
|
27
|
+
else
|
28
|
+
:external
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def max_threads
|
33
|
+
(
|
34
|
+
options[:max_threads] ||
|
35
|
+
env['GOOD_JOB_MAX_THREADS'] ||
|
36
|
+
env['RAILS_MAX_THREADS'] ||
|
37
|
+
ActiveRecord::Base.connection_pool.size
|
38
|
+
).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def queue_string
|
42
|
+
options[:queues] ||
|
43
|
+
env['GOOD_JOB_QUEUES'] ||
|
44
|
+
'*'
|
45
|
+
end
|
46
|
+
|
47
|
+
def poll_interval
|
48
|
+
(
|
49
|
+
options[:poll_interval] ||
|
50
|
+
env['GOOD_JOB_POLL_INTERVAL'] ||
|
51
|
+
1
|
52
|
+
).to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module GoodJob
|
2
|
+
# Thread-local attributes for passing values from Instrumentation.
|
3
|
+
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
4
|
+
|
5
|
+
module CurrentExecution
|
6
|
+
# @!attribute [rw] error_on_retry
|
7
|
+
# @!scope class
|
8
|
+
# Error captured by retry_on
|
9
|
+
# @return [Exception, nil]
|
10
|
+
thread_mattr_accessor :error_on_retry
|
11
|
+
|
12
|
+
# @!attribute [rw] error_on_discard
|
13
|
+
# @!scope class
|
14
|
+
# Error captured by discard_on
|
15
|
+
# @return [Exception, nil]
|
16
|
+
thread_mattr_accessor :error_on_discard
|
17
|
+
|
18
|
+
# Resets attributes
|
19
|
+
# @return [void]
|
20
|
+
def self.reset
|
21
|
+
self.error_on_retry = nil
|
22
|
+
self.error_on_discard = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Integer] Current process ID
|
26
|
+
def self.process_id
|
27
|
+
Process.pid
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] Current thread name
|
31
|
+
def self.thread_name
|
32
|
+
(Thread.current.name || Thread.current.object_id).to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/good_job/job.rb
CHANGED
@@ -9,6 +9,25 @@ module GoodJob
|
|
9
9
|
|
10
10
|
self.table_name = 'good_jobs'.freeze
|
11
11
|
|
12
|
+
def self.queue_parser(string)
|
13
|
+
string = string.presence || '*'
|
14
|
+
|
15
|
+
if string.first == '-'
|
16
|
+
exclude_queues = true
|
17
|
+
string = string[1..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
queues = string.split(',').map(&:strip)
|
21
|
+
|
22
|
+
if queues.include?('*')
|
23
|
+
{ all: true }
|
24
|
+
elsif exclude_queues
|
25
|
+
{ exclude: queues }
|
26
|
+
else
|
27
|
+
{ include: queues }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
12
31
|
scope :unfinished, (lambda do
|
13
32
|
if column_names.include?('finished_at')
|
14
33
|
where(finished_at: nil)
|
@@ -18,15 +37,26 @@ module GoodJob
|
|
18
37
|
end
|
19
38
|
end)
|
20
39
|
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Time.current)).or(where(scheduled_at: nil)) }
|
21
|
-
scope :priority_ordered, -> { order(priority
|
40
|
+
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
|
22
41
|
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
|
42
|
+
scope :queue_string, (lambda do |string|
|
43
|
+
parsed = queue_parser(string)
|
44
|
+
|
45
|
+
if parsed[:all]
|
46
|
+
all
|
47
|
+
elsif parsed[:exclude]
|
48
|
+
where.not(queue_name: parsed[:exclude]).or where(queue_name: nil)
|
49
|
+
elsif parsed[:include]
|
50
|
+
where(queue_name: parsed[:include])
|
51
|
+
end
|
52
|
+
end)
|
23
53
|
|
24
54
|
def self.perform_with_advisory_lock
|
25
55
|
good_job = nil
|
26
56
|
result = nil
|
27
57
|
error = nil
|
28
58
|
|
29
|
-
unfinished.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
59
|
+
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
30
60
|
good_job = good_jobs.first
|
31
61
|
break unless good_job
|
32
62
|
|
@@ -43,7 +73,7 @@ module GoodJob
|
|
43
73
|
queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
|
44
74
|
priority: active_job.priority || DEFAULT_PRIORITY,
|
45
75
|
serialized_params: active_job.serialize,
|
46
|
-
scheduled_at: scheduled_at
|
76
|
+
scheduled_at: scheduled_at,
|
47
77
|
create_with_advisory_lock: create_with_advisory_lock
|
48
78
|
)
|
49
79
|
|
@@ -59,11 +89,11 @@ module GoodJob
|
|
59
89
|
def perform(destroy_after: !GoodJob.preserve_job_records, reperform_on_standard_error: GoodJob.reperform_jobs_on_standard_error)
|
60
90
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
61
91
|
|
92
|
+
GoodJob::CurrentExecution.reset
|
62
93
|
result = nil
|
63
94
|
rescued_error = nil
|
64
95
|
error = nil
|
65
96
|
|
66
|
-
ActiveSupport::Notifications.instrument("before_perform_job.good_job", { good_job: self })
|
67
97
|
self.performed_at = Time.current
|
68
98
|
save! unless destroy_after
|
69
99
|
|
@@ -72,18 +102,23 @@ module GoodJob
|
|
72
102
|
)
|
73
103
|
|
74
104
|
begin
|
75
|
-
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
|
105
|
+
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
76
106
|
result = ActiveJob::Base.execute(params)
|
77
107
|
end
|
78
108
|
rescue StandardError => e
|
79
109
|
rescued_error = e
|
80
110
|
end
|
81
111
|
|
112
|
+
retry_or_discard_error = GoodJob::CurrentExecution.error_on_retry ||
|
113
|
+
GoodJob::CurrentExecution.error_on_discard
|
114
|
+
|
82
115
|
if rescued_error
|
83
116
|
error = rescued_error
|
84
117
|
elsif result.is_a?(Exception)
|
85
118
|
error = result
|
86
119
|
result = nil
|
120
|
+
elsif retry_or_discard_error
|
121
|
+
error = retry_or_discard_error
|
87
122
|
end
|
88
123
|
|
89
124
|
error_message = "#{error.class}: #{error.message}" if error
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module GoodJob
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def create(event)
|
4
|
+
good_job = event.payload[:good_job]
|
5
|
+
|
6
|
+
debug do
|
7
|
+
"GoodJob created job resource with id #{good_job.id}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def timer_task_finished(event)
|
12
|
+
exception = event.payload[:error]
|
13
|
+
return unless exception
|
14
|
+
|
15
|
+
error do
|
16
|
+
"GoodJob error: #{exception}\n #{exception.backtrace}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def job_finished(event)
|
21
|
+
exception = event.payload[:error]
|
22
|
+
return unless exception
|
23
|
+
|
24
|
+
error do
|
25
|
+
"GoodJob error: #{exception}\n #{exception.backtrace}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def scheduler_create_pools(event)
|
30
|
+
max_threads = event.payload[:max_threads]
|
31
|
+
poll_interval = event.payload[:poll_interval]
|
32
|
+
performer_name = event.payload[:performer_name]
|
33
|
+
process_id = event.payload[:process_id]
|
34
|
+
|
35
|
+
info(tags: [process_id]) do
|
36
|
+
"GoodJob started scheduler with queues=#{performer_name} max_threads=#{max_threads} poll_interval=#{poll_interval}."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def scheduler_shutdown_start(event)
|
41
|
+
process_id = event.payload[:process_id]
|
42
|
+
|
43
|
+
info(tags: [process_id]) do
|
44
|
+
"GoodJob shutting down scheduler..."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def scheduler_shutdown(event)
|
49
|
+
process_id = event.payload[:process_id]
|
50
|
+
|
51
|
+
info(tags: [process_id]) do
|
52
|
+
"GoodJob scheduler is shut down."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def scheduler_restart_pools(event)
|
57
|
+
process_id = event.payload[:process_id]
|
58
|
+
|
59
|
+
info(tags: [process_id]) do
|
60
|
+
"GoodJob scheduler has restarted."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def perform_job(event)
|
65
|
+
good_job = event.payload[:good_job]
|
66
|
+
process_id = event.payload[:process_id]
|
67
|
+
thread_name = event.payload[:thread_name]
|
68
|
+
|
69
|
+
info(tags: [process_id, thread_name]) do
|
70
|
+
"Executed GoodJob #{good_job.id}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def notifier_listen(_event)
|
75
|
+
info do
|
76
|
+
"Notifier subscribed with LISTEN"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def notifier_notified(event)
|
81
|
+
payload = event.payload[:payload]
|
82
|
+
|
83
|
+
debug do
|
84
|
+
"Notifier received payload: #{payload}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def notifier_notify_error(event)
|
89
|
+
error = event.payload[:error]
|
90
|
+
|
91
|
+
error do
|
92
|
+
"Notifier errored: #{error}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def notifier_unlisten(_event)
|
97
|
+
info do
|
98
|
+
"Notifier unsubscribed with UNLISTEN"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def cleanup_preserved_jobs(event)
|
103
|
+
timestamp = event.payload[:timestamp]
|
104
|
+
deleted_records_count = event.payload[:deleted_records_count]
|
105
|
+
|
106
|
+
info do
|
107
|
+
"GoodJob deleted #{deleted_records_count} preserved #{'job'.pluralize(deleted_records_count)} finished before #{timestamp}."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class << self
|
112
|
+
def loggers
|
113
|
+
@_loggers ||= [GoodJob.logger]
|
114
|
+
end
|
115
|
+
|
116
|
+
def logger
|
117
|
+
@_logger ||= begin
|
118
|
+
logger = Logger.new(StringIO.new)
|
119
|
+
loggers.each do |each_logger|
|
120
|
+
logger.extend(ActiveSupport::Logger.broadcast(each_logger))
|
121
|
+
end
|
122
|
+
logger
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def reset_logger
|
127
|
+
@_logger = nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def logger
|
132
|
+
GoodJob::LogSubscriber.logger
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def tag_logger(*tags, &block)
|
138
|
+
tags = tags.dup.unshift("GoodJob").compact
|
139
|
+
|
140
|
+
self.class.loggers.inject(block) do |inner, each_logger|
|
141
|
+
if each_logger.respond_to?(:tagged)
|
142
|
+
tags_for_logger = if each_logger.formatter.current_tags.include?("ActiveJob")
|
143
|
+
["ActiveJob"] + tags
|
144
|
+
else
|
145
|
+
tags
|
146
|
+
end
|
147
|
+
|
148
|
+
proc { each_logger.tagged(*tags_for_logger, &inner) }
|
149
|
+
else
|
150
|
+
inner
|
151
|
+
end
|
152
|
+
end.call
|
153
|
+
end
|
154
|
+
|
155
|
+
%w(info debug warn error fatal unknown).each do |level|
|
156
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
157
|
+
def #{level}(progname = nil, tags: [], &block)
|
158
|
+
return unless logger
|
159
|
+
|
160
|
+
tag_logger(*tags) do
|
161
|
+
logger.#{level}(progname, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
METHOD
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|