activejob 5.2.7.1 → 6.1.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +109 -58
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +18 -13
  5. data/lib/active_job/arguments.rb +80 -30
  6. data/lib/active_job/base.rb +6 -1
  7. data/lib/active_job/callbacks.rb +46 -3
  8. data/lib/active_job/configured_job.rb +2 -0
  9. data/lib/active_job/core.rb +40 -21
  10. data/lib/active_job/enqueuing.rb +20 -7
  11. data/lib/active_job/exceptions.rb +60 -28
  12. data/lib/active_job/execution.rb +11 -2
  13. data/lib/active_job/gem_version.rb +4 -4
  14. data/lib/active_job/instrumentation.rb +40 -0
  15. data/lib/active_job/log_subscriber.rb +140 -0
  16. data/lib/active_job/logging.rb +3 -101
  17. data/lib/active_job/queue_adapter.rb +5 -0
  18. data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
  19. data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
  20. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/que_adapter.rb +2 -2
  22. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
  23. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +1 -1
  24. data/lib/active_job/queue_adapters/test_adapter.rb +32 -14
  25. data/lib/active_job/queue_adapters.rb +13 -11
  26. data/lib/active_job/queue_name.rb +23 -3
  27. data/lib/active_job/railtie.rb +20 -1
  28. data/lib/active_job/serializers/date_serializer.rb +20 -0
  29. data/lib/active_job/serializers/date_time_serializer.rb +16 -0
  30. data/lib/active_job/serializers/duration_serializer.rb +23 -0
  31. data/lib/active_job/serializers/module_serializer.rb +20 -0
  32. data/lib/active_job/serializers/object_serializer.rb +53 -0
  33. data/lib/active_job/serializers/symbol_serializer.rb +20 -0
  34. data/lib/active_job/serializers/time_object_serializer.rb +13 -0
  35. data/lib/active_job/serializers/time_serializer.rb +16 -0
  36. data/lib/active_job/serializers/time_with_zone_serializer.rb +16 -0
  37. data/lib/active_job/serializers.rb +66 -0
  38. data/lib/active_job/test_helper.rb +317 -68
  39. data/lib/active_job/timezones.rb +13 -0
  40. data/lib/active_job/translation.rb +1 -1
  41. data/lib/active_job.rb +2 -1
  42. data/lib/rails/generators/job/job_generator.rb +4 -0
  43. metadata +26 -11
  44. data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -6,32 +6,42 @@ module ActiveJob
6
6
  module Core
7
7
  extend ActiveSupport::Concern
8
8
 
9
- included do
10
- # Job arguments
11
- attr_accessor :arguments
12
- attr_writer :serialized_arguments
9
+ # Job arguments
10
+ attr_accessor :arguments
11
+ attr_writer :serialized_arguments
13
12
 
14
- # Timestamp when the job should be performed
15
- attr_accessor :scheduled_at
13
+ # Timestamp when the job should be performed
14
+ attr_accessor :scheduled_at
16
15
 
17
- # Job Identifier
18
- attr_accessor :job_id
16
+ # Job Identifier
17
+ attr_accessor :job_id
19
18
 
20
- # Queue in which the job will reside.
21
- attr_writer :queue_name
19
+ # Queue in which the job will reside.
20
+ attr_writer :queue_name
22
21
 
23
- # Priority that the job will have (lower is more priority).
24
- attr_writer :priority
22
+ # Priority that the job will have (lower is more priority).
23
+ attr_writer :priority
25
24
 
26
- # ID optionally provided by adapter
27
- attr_accessor :provider_job_id
25
+ # ID optionally provided by adapter
26
+ attr_accessor :provider_job_id
28
27
 
29
- # Number of times this job has been executed (which increments on every retry, like after an exception).
30
- attr_accessor :executions
28
+ # Number of times this job has been executed (which increments on every retry, like after an exception).
29
+ attr_accessor :executions
31
30
 
32
- # I18n.locale to be used during the job.
33
- attr_accessor :locale
34
- end
31
+ # Hash that contains the number of times this job handled errors for each specific retry_on declaration.
32
+ # Keys are the string representation of the exceptions listed in the retry_on declaration,
33
+ # while its associated value holds the number of executions where the corresponding retry_on
34
+ # declaration handled one of its listed exceptions.
35
+ attr_accessor :exception_executions
36
+
37
+ # I18n.locale to be used during the job.
38
+ attr_accessor :locale
39
+
40
+ # Timezone to be used during the job.
41
+ attr_accessor :timezone
42
+
43
+ # Track when a job was enqueued
44
+ attr_accessor :enqueued_at
35
45
 
36
46
  # These methods will be included into any Active Job object, adding
37
47
  # helpers for de/serialization and creation of job instances.
@@ -74,10 +84,13 @@ module ActiveJob
74
84
  @queue_name = self.class.queue_name
75
85
  @priority = self.class.priority
76
86
  @executions = 0
87
+ @exception_executions = {}
88
+ @timezone = Time.zone&.name
77
89
  end
90
+ ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
78
91
 
79
92
  # Returns a hash with the job data that can safely be passed to the
80
- # queueing adapter.
93
+ # queuing adapter.
81
94
  def serialize
82
95
  {
83
96
  "job_class" => self.class.name,
@@ -87,7 +100,10 @@ module ActiveJob
87
100
  "priority" => priority,
88
101
  "arguments" => serialize_arguments_if_needed(arguments),
89
102
  "executions" => executions,
90
- "locale" => I18n.locale.to_s
103
+ "exception_executions" => exception_executions,
104
+ "locale" => I18n.locale.to_s,
105
+ "timezone" => timezone,
106
+ "enqueued_at" => Time.now.utc.iso8601
91
107
  }
92
108
  end
93
109
 
@@ -124,7 +140,10 @@ module ActiveJob
124
140
  self.priority = job_data["priority"]
125
141
  self.serialized_arguments = job_data["arguments"]
126
142
  self.executions = job_data["executions"]
143
+ self.exception_executions = job_data["exception_executions"]
127
144
  self.locale = job_data["locale"] || I18n.locale.to_s
145
+ self.timezone = job_data["timezone"] || Time.zone&.name
146
+ self.enqueued_at = job_data["enqueued_at"]
128
147
  end
129
148
 
130
149
  private
@@ -9,21 +9,25 @@ module ActiveJob
9
9
 
10
10
  # Includes the +perform_later+ method for job initialization.
11
11
  module ClassMethods
12
- # Push a job onto the queue. The arguments must be legal JSON types
13
- # (+string+, +int+, +float+, +nil+, +true+, +false+, +hash+ or +array+) or
14
- # GlobalID::Identification instances. Arbitrary Ruby objects
15
- # are not supported.
12
+ # Push a job onto the queue. By default the arguments must be either String,
13
+ # Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date,
14
+ # Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration,
15
+ # Hash, ActiveSupport::HashWithIndifferentAccess, Array or
16
+ # GlobalID::Identification instances, although this can be extended by adding
17
+ # custom serializers.
16
18
  #
17
19
  # Returns an instance of the job class queued with arguments available in
18
20
  # Job#arguments.
19
21
  def perform_later(*args)
20
22
  job_or_instantiate(*args).enqueue
21
23
  end
24
+ ruby2_keywords(:perform_later) if respond_to?(:ruby2_keywords, true)
22
25
 
23
26
  private
24
27
  def job_or_instantiate(*args) # :doc:
25
28
  args.first.is_a?(self) ? args.first : new(*args)
26
29
  end
30
+ ruby2_keywords(:job_or_instantiate) if respond_to?(:ruby2_keywords, true)
27
31
  end
28
32
 
29
33
  # Enqueues the job to be performed by the queue adapter.
@@ -46,14 +50,23 @@ module ActiveJob
46
50
  self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
47
51
  self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
48
52
  self.priority = options[:priority].to_i if options[:priority]
53
+ successfully_enqueued = false
54
+
49
55
  run_callbacks :enqueue do
50
56
  if scheduled_at
51
- self.class.queue_adapter.enqueue_at self, scheduled_at
57
+ queue_adapter.enqueue_at self, scheduled_at
52
58
  else
53
- self.class.queue_adapter.enqueue self
59
+ queue_adapter.enqueue self
54
60
  end
61
+
62
+ successfully_enqueued = true
63
+ end
64
+
65
+ if successfully_enqueued
66
+ self
67
+ else
68
+ false
55
69
  end
56
- self
57
70
  end
58
71
  end
59
72
  end
@@ -7,6 +7,10 @@ module ActiveJob
7
7
  module Exceptions
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ included do
11
+ class_attribute :retry_jitter, instance_accessor: false, instance_predicate: false, default: 0.0
12
+ end
13
+
10
14
  module ClassMethods
11
15
  # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
12
16
  # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
@@ -18,40 +22,49 @@ module ActiveJob
18
22
  #
19
23
  # ==== Options
20
24
  # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
21
- # as a computing proc that the number of executions so far as an argument, or as a symbol reference of
22
- # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
23
- # (first wait 3s, then 18s, then 83s, etc)
25
+ # as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of
26
+ # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</tt>
27
+ # (first wait ~3s, then ~18s, then ~83s, etc)
24
28
  # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
25
29
  # * <tt>:queue</tt> - Re-enqueues the job on a different queue
26
30
  # * <tt>:priority</tt> - Re-enqueues the job with a different priority
31
+ # * <tt>:jitter</tt> - A random delay of wait time used when calculating backoff. The default is 15% (0.15) which represents the upper bound of possible wait time (expressed as a percentage)
27
32
  #
28
33
  # ==== Examples
29
34
  #
30
35
  # class RemoteServiceJob < ActiveJob::Base
31
- # retry_on CustomAppException # defaults to 3s wait, 5 attempts
36
+ # retry_on CustomAppException # defaults to ~3s wait, 5 attempts
32
37
  # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
38
+ #
39
+ # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
40
+ # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
41
+ # # To retry at most 10 times for each individual exception:
42
+ # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
43
+ # # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
44
+ # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
45
+ #
33
46
  # retry_on(YetAnotherCustomAppException) do |job, error|
34
47
  # ExceptionNotifier.caught(error)
35
48
  # end
36
- # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
37
- # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
38
49
  #
39
50
  # def perform(*args)
40
51
  # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
41
52
  # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
42
- # # Might raise Net::OpenTimeout when the remote service is down
53
+ # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
43
54
  # end
44
55
  # end
45
- def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
46
- rescue_from exception do |error|
56
+ def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
57
+ rescue_from(*exceptions) do |error|
58
+ executions = executions_for(exceptions)
47
59
  if executions < attempts
48
- logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
49
- retry_job wait: determine_delay(wait), queue: queue, priority: priority
60
+ retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions, jitter: jitter), queue: queue, priority: priority, error: error
50
61
  else
51
62
  if block_given?
52
- yield self, error
63
+ instrument :retry_stopped, error: error do
64
+ yield self, error
65
+ end
53
66
  else
54
- logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
67
+ instrument :retry_stopped, error: error
55
68
  raise error
56
69
  end
57
70
  end
@@ -76,12 +89,10 @@ module ActiveJob
76
89
  # # Might raise CustomAppException for something domain specific
77
90
  # end
78
91
  # end
79
- def discard_on(exception)
80
- rescue_from exception do |error|
81
- if block_given?
82
- yield self, error
83
- else
84
- logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
92
+ def discard_on(*exceptions)
93
+ rescue_from(*exceptions) do |error|
94
+ instrument :discard, error: error do
95
+ yield self, error if block_given?
85
96
  end
86
97
  end
87
98
  end
@@ -109,20 +120,27 @@ module ActiveJob
109
120
  # end
110
121
  # end
111
122
  def retry_job(options = {})
112
- enqueue options
123
+ instrument :enqueue_retry, options.slice(:error, :wait) do
124
+ enqueue options
125
+ end
113
126
  end
114
127
 
115
128
  private
116
- def determine_delay(seconds_or_duration_or_algorithm)
129
+ JITTER_DEFAULT = Object.new
130
+ private_constant :JITTER_DEFAULT
131
+
132
+ def determine_delay(seconds_or_duration_or_algorithm:, executions:, jitter: JITTER_DEFAULT)
133
+ jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
134
+
117
135
  case seconds_or_duration_or_algorithm
118
136
  when :exponentially_longer
119
- (executions**4) + 2
120
- when ActiveSupport::Duration
121
- duration = seconds_or_duration_or_algorithm
122
- duration.to_i
123
- when Integer
124
- seconds = seconds_or_duration_or_algorithm
125
- seconds
137
+ delay = executions**4
138
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
139
+ delay + delay_jitter + 2
140
+ when ActiveSupport::Duration, Integer
141
+ delay = seconds_or_duration_or_algorithm.to_i
142
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
143
+ delay + delay_jitter
126
144
  when Proc
127
145
  algorithm = seconds_or_duration_or_algorithm
128
146
  algorithm.call(executions)
@@ -130,5 +148,19 @@ module ActiveJob
130
148
  raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
131
149
  end
132
150
  end
151
+
152
+ def determine_jitter_for_delay(delay, jitter)
153
+ return 0.0 if jitter.zero?
154
+ Kernel.rand * delay * jitter
155
+ end
156
+
157
+ def executions_for(exceptions)
158
+ if exception_executions
159
+ exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1
160
+ else
161
+ # Guard against jobs that were persisted before we started having individual executions counters per retry_on
162
+ executions
163
+ end
164
+ end
133
165
  end
134
166
  end
@@ -17,6 +17,7 @@ module ActiveJob
17
17
  def perform_now(*args)
18
18
  job_or_instantiate(*args).perform_now
19
19
  end
20
+ ruby2_keywords(:perform_now) if respond_to?(:ruby2_keywords, true)
20
21
 
21
22
  def execute(job_data) #:nodoc:
22
23
  ActiveJob::Callbacks.run_callbacks(:execute) do
@@ -26,15 +27,23 @@ module ActiveJob
26
27
  end
27
28
  end
28
29
 
29
- # Performs the job immediately. The job is not sent to the queueing adapter
30
+ # Performs the job immediately. The job is not sent to the queuing adapter
30
31
  # but directly executed by blocking the execution of others until it's finished.
32
+ # +perform_now+ returns the value of your job's +perform+ method.
31
33
  #
32
- # MyJob.new(*args).perform_now
34
+ # class MyJob < ActiveJob::Base
35
+ # def perform
36
+ # "Hello World!"
37
+ # end
38
+ # end
39
+ #
40
+ # puts MyJob.new(*args).perform_now # => "Hello World!"
33
41
  def perform_now
34
42
  # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
35
43
  self.executions = (executions || 0) + 1
36
44
 
37
45
  deserialize_arguments_if_needed
46
+
38
47
  run_callbacks :perform do
39
48
  perform(*arguments)
40
49
  end
@@ -7,10 +7,10 @@ module ActiveJob
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 7
13
- PRE = "1"
10
+ MAJOR = 6
11
+ MINOR = 1
12
+ TINY = 4
13
+ PRE = "6"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Instrumentation #:nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ around_enqueue do |_, block|
9
+ scheduled_at ? instrument(:enqueue_at, &block) : instrument(:enqueue, &block)
10
+ end
11
+
12
+ around_perform do |_, block|
13
+ instrument :perform_start
14
+ instrument :perform, &block
15
+ end
16
+ end
17
+
18
+ private
19
+ def instrument(operation, payload = {}, &block)
20
+ enhanced_block = ->(event_payload) do
21
+ value = block.call if block
22
+
23
+ if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
24
+ event_payload[:aborted] = true
25
+ @_halted_callback_hook_called = nil
26
+ end
27
+
28
+ value
29
+ end
30
+
31
+ ActiveSupport::Notifications.instrument \
32
+ "#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
33
+ end
34
+
35
+ def halted_callback_hook(*)
36
+ super
37
+ @_halted_callback_hook_called = true
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/log_subscriber"
5
+
6
+ module ActiveJob
7
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
8
+ def enqueue(event)
9
+ job = event.payload[:job]
10
+ ex = event.payload[:exception_object]
11
+
12
+ if ex
13
+ error do
14
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
15
+ end
16
+ elsif event.payload[:aborted]
17
+ info do
18
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
19
+ end
20
+ else
21
+ info do
22
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
23
+ end
24
+ end
25
+ end
26
+
27
+ def enqueue_at(event)
28
+ job = event.payload[:job]
29
+ ex = event.payload[:exception_object]
30
+
31
+ if ex
32
+ error do
33
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
34
+ end
35
+ elsif event.payload[:aborted]
36
+ info do
37
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
38
+ end
39
+ else
40
+ info do
41
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
42
+ end
43
+ end
44
+ end
45
+
46
+ def perform_start(event)
47
+ info do
48
+ job = event.payload[:job]
49
+ "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
50
+ end
51
+ end
52
+
53
+ def perform(event)
54
+ job = event.payload[:job]
55
+ ex = event.payload[:exception_object]
56
+ if ex
57
+ error do
58
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
59
+ end
60
+ elsif event.payload[:aborted]
61
+ error do
62
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: a before_perform callback halted the job execution"
63
+ end
64
+ else
65
+ info do
66
+ "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
67
+ end
68
+ end
69
+ end
70
+
71
+ def enqueue_retry(event)
72
+ job = event.payload[:job]
73
+ ex = event.payload[:error]
74
+ wait = event.payload[:wait]
75
+
76
+ info do
77
+ if ex
78
+ "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
79
+ else
80
+ "Retrying #{job.class} in #{wait.to_i} seconds."
81
+ end
82
+ end
83
+ end
84
+
85
+ def retry_stopped(event)
86
+ job = event.payload[:job]
87
+ ex = event.payload[:error]
88
+
89
+ error do
90
+ "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
91
+ end
92
+ end
93
+
94
+ def discard(event)
95
+ job = event.payload[:job]
96
+ ex = event.payload[:error]
97
+
98
+ error do
99
+ "Discarded #{job.class} due to a #{ex.class}."
100
+ end
101
+ end
102
+
103
+ private
104
+ def queue_name(event)
105
+ event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
106
+ end
107
+
108
+ def args_info(job)
109
+ if job.class.log_arguments? && job.arguments.any?
110
+ " with arguments: " +
111
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
112
+ else
113
+ ""
114
+ end
115
+ end
116
+
117
+ def format(arg)
118
+ case arg
119
+ when Hash
120
+ arg.transform_values { |value| format(value) }
121
+ when Array
122
+ arg.map { |value| format(value) }
123
+ when GlobalID::Identification
124
+ arg.to_global_id rescue arg
125
+ else
126
+ arg
127
+ end
128
+ end
129
+
130
+ def scheduled_at(event)
131
+ Time.at(event.payload[:job].scheduled_at).utc
132
+ end
133
+
134
+ def logger
135
+ ActiveJob::Base.logger
136
+ end
137
+ end
138
+ end
139
+
140
+ ActiveJob::LogSubscriber.attach_to :active_job
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/transform_values"
4
- require "active_support/core_ext/string/filters"
5
3
  require "active_support/tagged_logging"
6
4
  require "active_support/logger"
7
5
 
@@ -11,32 +9,10 @@ module ActiveJob
11
9
 
12
10
  included do
13
11
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
12
+ class_attribute :log_arguments, instance_accessor: false, default: true
14
13
 
15
- around_enqueue do |_, block, _|
16
- tag_logger do
17
- block.call
18
- end
19
- end
20
-
21
- around_perform do |job, block, _|
22
- tag_logger(job.class.name, job.job_id) do
23
- payload = { adapter: job.class.queue_adapter, job: job }
24
- ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
25
- ActiveSupport::Notifications.instrument("perform.active_job", payload) do
26
- block.call
27
- end
28
- end
29
- end
30
-
31
- after_enqueue do |job|
32
- if job.scheduled_at
33
- ActiveSupport::Notifications.instrument "enqueue_at.active_job",
34
- adapter: job.class.queue_adapter, job: job
35
- else
36
- ActiveSupport::Notifications.instrument "enqueue.active_job",
37
- adapter: job.class.queue_adapter, job: job
38
- end
39
- end
14
+ around_enqueue { |_, block| tag_logger(&block) }
15
+ around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
40
16
  end
41
17
 
42
18
  private
@@ -52,79 +28,5 @@ module ActiveJob
52
28
  def logger_tagged_by_active_job?
53
29
  logger.formatter.current_tags.include?("ActiveJob")
54
30
  end
55
-
56
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
57
- def enqueue(event)
58
- info do
59
- job = event.payload[:job]
60
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
61
- end
62
- end
63
-
64
- def enqueue_at(event)
65
- info do
66
- job = event.payload[:job]
67
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
68
- end
69
- end
70
-
71
- def perform_start(event)
72
- info do
73
- job = event.payload[:job]
74
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
75
- end
76
- end
77
-
78
- def perform(event)
79
- job = event.payload[:job]
80
- ex = event.payload[:exception_object]
81
- if ex
82
- error do
83
- "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
84
- end
85
- else
86
- info do
87
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
88
- end
89
- end
90
- end
91
-
92
- private
93
- def queue_name(event)
94
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
95
- end
96
-
97
- def args_info(job)
98
- if job.arguments.any?
99
- " with arguments: " +
100
- job.arguments.map { |arg| format(arg).inspect }.join(", ")
101
- else
102
- ""
103
- end
104
- end
105
-
106
- def format(arg)
107
- case arg
108
- when Hash
109
- arg.transform_values { |value| format(value) }
110
- when Array
111
- arg.map { |value| format(value) }
112
- when GlobalID::Identification
113
- arg.to_global_id rescue arg
114
- else
115
- arg
116
- end
117
- end
118
-
119
- def scheduled_at(event)
120
- Time.at(event.payload[:job].scheduled_at).utc
121
- end
122
-
123
- def logger
124
- ActiveJob::Base.logger
125
- end
126
- end
127
31
  end
128
32
  end
129
-
130
- ActiveJob::Logging::LogSubscriber.attach_to :active_job
@@ -11,6 +11,9 @@ module ActiveJob
11
11
  included do
12
12
  class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
13
13
  class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
14
+
15
+ delegate :queue_adapter, to: :class
16
+
14
17
  self.queue_adapter = :async
15
18
  end
16
19
 
@@ -22,6 +25,8 @@ module ActiveJob
22
25
  _queue_adapter
23
26
  end
24
27
 
28
+ # Returns string denoting the name of the configured queue adapter.
29
+ # By default returns <tt>"async"</tt>.
25
30
  def queue_adapter_name
26
31
  _queue_adapter_name
27
32
  end