rails_semantic_logger 4.20.0 → 5.1.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +55 -98
  3. data/Rakefile +9 -4
  4. data/lib/rails_semantic_logger/action_controller/log_subscriber.rb +87 -16
  5. data/lib/rails_semantic_logger/action_mailer/log_subscriber.rb +36 -22
  6. data/lib/rails_semantic_logger/action_view/log_subscriber.rb +76 -42
  7. data/lib/rails_semantic_logger/active_job/log_subscriber.rb +216 -7
  8. data/lib/rails_semantic_logger/active_record/log_subscriber.rb +62 -160
  9. data/lib/rails_semantic_logger/appenders.rb +92 -0
  10. data/lib/rails_semantic_logger/engine.rb +68 -57
  11. data/lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb +61 -3
  12. data/lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb +5 -14
  13. data/lib/rails_semantic_logger/extensions/active_job/logging.rb +2 -2
  14. data/lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb +2 -2
  15. data/lib/rails_semantic_logger/extensions/active_support/logger.rb +24 -15
  16. data/lib/rails_semantic_logger/extensions/rails/server.rb +1 -1
  17. data/lib/rails_semantic_logger/options.rb +171 -20
  18. data/lib/rails_semantic_logger/rack/logger.rb +6 -13
  19. data/lib/rails_semantic_logger/sidekiq/defaults.rb +12 -27
  20. data/lib/rails_semantic_logger/sidekiq/job_logger.rb +29 -9
  21. data/lib/rails_semantic_logger/solid_queue/log_subscriber.rb +183 -0
  22. data/lib/rails_semantic_logger/version.rb +1 -1
  23. data/lib/rails_semantic_logger.rb +82 -27
  24. metadata +16 -22
  25. data/lib/rails_semantic_logger/delayed_job/plugin.rb +0 -11
  26. data/lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb +0 -13
  27. data/lib/rails_semantic_logger/extensions/rack/server.rb +0 -12
  28. data/lib/rails_semantic_logger/extensions/rackup/server.rb +0 -12
  29. data/lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb +0 -87
@@ -6,21 +6,12 @@ module ActionDispatch
6
6
  private
7
7
 
8
8
  undef_method :log_error
9
- if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
10
- def log_error(request, wrapper)
11
- Rails.application.deprecators.silence do
12
- return if !log_rescued_responses?(request) && wrapper.rescue_response?
9
+ def log_error(request, wrapper)
10
+ Rails.application.deprecators.silence do
11
+ return if !log_rescued_responses?(request) && wrapper.rescue_response?
13
12
 
14
- level = request.get_header("action_dispatch.debug_exception_log_level")
15
- ActionController::Base.logger.log(level, wrapper.exception)
16
- end
17
- end
18
- else
19
- def log_error(_request, wrapper)
20
- ActiveSupport::Deprecation.silence do
21
- level = wrapper.respond_to?(:rescue_response?) && wrapper.rescue_response? ? :debug : :fatal
22
- ActionController::Base.logger.log(level, wrapper.exception)
23
- end
13
+ level = request.get_header("action_dispatch.debug_exception_log_level")
14
+ ActionController::Base.logger.log(level, wrapper.exception)
24
15
  end
25
16
  end
26
17
  end
@@ -8,9 +8,9 @@ module ActiveJob
8
8
  private
9
9
 
10
10
  undef_method :tag_logger
11
- def tag_logger(*tags, &block)
11
+ def tag_logger(*tags, &)
12
12
  if logger.respond_to?(:tagged)
13
- logger.tagged(*tags, &block)
13
+ logger.tagged(*tags, &)
14
14
  else
15
15
  yield
16
16
  end
@@ -7,8 +7,8 @@ module ActiveModelSerializers
7
7
 
8
8
  private
9
9
 
10
- def tag_logger(*tags, &block)
11
- logger.tagged(*tags, &block)
10
+ def tag_logger(*tags, &)
11
+ logger.tagged(*tags, &)
12
12
  end
13
13
  end
14
14
 
@@ -1,27 +1,36 @@
1
1
  require "active_support/logger"
2
2
 
3
3
  module ActiveSupport
4
- # More hacks to try and stop Rails from being it's own worst enemy.
4
+ # More hacks to try and stop Rails from being its own worst enemy.
5
5
  class Logger
6
6
  class << self
7
+ # Keep a handle on the genuine constructor so that callers which supply a
8
+ # real log destination still get a real logger (see #new below).
9
+ alias _semantic_logger_original_new new
10
+
7
11
  undef :logger_outputs_to?
8
12
 
9
- # Prevent broadcasting since SemanticLogger already supports multiple loggers
10
- if method_defined?(:broadcast)
11
- undef :broadcast
12
- def broadcast(_logger)
13
- Module.new
14
- end
13
+ # Prevent Rails from trying to merge/broadcast loggers (e.g. ActiveRecord's
14
+ # `console` hook and `rails server`'s log_to_stdout). SemanticLogger already
15
+ # multiplexes through its own appenders, and SemanticLogger::Logger does not
16
+ # implement #broadcast_to, so the merge path would otherwise raise.
17
+ def logger_outputs_to?(*_args)
18
+ true
15
19
  end
16
- end
17
20
 
18
- # Prevent Console from trying to merge loggers
19
- def self.logger_outputs_to?(*_args)
20
- true
21
- end
22
-
23
- def self.new(*_args, **_kwargs)
24
- SemanticLogger[self]
21
+ # Historically every `ActiveSupport::Logger.new(...)` call was redirected to
22
+ # SemanticLogger, silently discarding the requested destination. That broke
23
+ # third-party callers such as Webpacker's `ActiveSupport::Logger.new(STDOUT)`,
24
+ # whose output never reached STDOUT (issue #141). Only redirect to
25
+ # SemanticLogger when no destination is supplied; otherwise honor the caller
26
+ # and build a genuine logger pointed at the requested destination.
27
+ def new(*args, **kwargs)
28
+ if args.empty? && kwargs.empty?
29
+ SemanticLogger[self]
30
+ else
31
+ _semantic_logger_original_new(*args, **kwargs)
32
+ end
33
+ end
25
34
  end
26
35
  end
27
36
  end
@@ -9,7 +9,7 @@ module Rails
9
9
  def log_to_stdout
10
10
  wrapped_app # touch the app so the logger is set up
11
11
 
12
- SemanticLogger.add_appender(io: $stdout, formatter: :color) unless SemanticLogger.appenders.console_output?
12
+ RailsSemanticLogger.add_server_appenders
13
13
  end
14
14
  end
15
15
  end
@@ -4,7 +4,9 @@ module RailsSemanticLogger
4
4
  # * Convert Action Controller and Active Record text messages to semantic data
5
5
  #
6
6
  # Rails -- Started -- { :ip => "127.0.0.1", :method => "GET", :path => "/dashboards/inquiry_recent_activity" }
7
+ # rubocop:disable Layout/LineLength
7
8
  # UserController -- Completed #index -- { :action => "index", :db_runtime => 54.64, :format => "HTML", :method => "GET", :mongo_runtime => 0.0, :path => "/users", :status => 200, :status_message => "OK", :view_runtime => 709.88 }
9
+ # rubocop:enable Layout/LineLength
8
10
  #
9
11
  # config.rails_semantic_logger.semantic = true
10
12
  #
@@ -44,6 +46,10 @@ module RailsSemanticLogger
44
46
  #
45
47
  # config.rails_semantic_logger.add_file_appender = true
46
48
  #
49
+ # DEPRECATED: declare appenders via #appenders instead. Declaring any appender there already
50
+ # replaces the default file appender, so this flag is no longer needed:
51
+ # config.rails_semantic_logger.appenders { |appenders| appenders.add(file_name: ...) }
52
+ #
47
53
  # * Silence asset logging
48
54
  #
49
55
  # config.rails_semantic_logger.quiet_assets = false
@@ -52,6 +58,10 @@ module RailsSemanticLogger
52
58
  #
53
59
  # config.rails_semantic_logger.console_logger = false
54
60
  #
61
+ # DEPRECATED: declare a console appender explicitly via #appenders instead, or
62
+ # declare none to disable it:
63
+ # config.rails_semantic_logger.appenders { |appenders| appenders.add_console(...) }
64
+ #
55
65
  # * Override the output format for the primary Rails log file.
56
66
  #
57
67
  # Valid options:
@@ -97,10 +107,6 @@ module RailsSemanticLogger
97
107
  #
98
108
  # config.rails_semantic_logger.filter = nil
99
109
  #
100
- # * named_tags: *DEPRECATED*
101
- # Instead, supply a Hash to config.log_tags
102
- # config.rails_semantic_logger.named_tags = nil
103
- #
104
110
  # * Change the message format of Action Controller action.
105
111
  # A block that will be called to format the message.
106
112
  # It is supplied with the `message` and `payload` and should return the formatted data.
@@ -112,26 +118,171 @@ module RailsSemanticLogger
112
118
  # * Do not replace the Sidekiq logger with a Semantic Logger logger.
113
119
  #
114
120
  # config.rails_semantic_logger.replace_sidekiq_logger = false
121
+ #
122
+ # * Do not replace the SolidQueue logger / log subscriber.
123
+ #
124
+ # config.rails_semantic_logger.replace_solid_queue_logger = false
115
125
  class Options
116
- attr_accessor :semantic, :started, :processing, :rendered, :ap_options, :add_file_appender,
117
- :quiet_assets, :format, :named_tags, :filter, :console_logger, :action_message_format,
118
- :replace_sidekiq_logger
126
+ # Settings consumed while the logger itself is built, during Rails'
127
+ # `:initialize_logger` initializer. Changing them after that (e.g. from
128
+ # `config/initializers/*`, which Rails loads later) has no effect.
129
+ LOGGER_INIT_SETTINGS = %i[semantic replace_sidekiq_logger replace_solid_queue_logger].freeze
130
+
131
+ # Settings consumed as Rails finishes initializing (`config.after_initialize`),
132
+ # which runs *after* `config/initializers/*`. They may still be set there, but
133
+ # changing them after the application has booted has no effect.
134
+ POST_INIT_SETTINGS = %i[started processing rendered quiet_assets action_message_format].freeze
135
+
136
+ attr_reader(*LOGGER_INIT_SETTINGS, *POST_INIT_SETTINGS)
137
+
138
+ # DEPRECATED: configure these on the appender instead, via #appenders.
139
+ attr_reader :ap_options, :format, :filter, :console_logger, :add_file_appender
140
+
141
+ LOGGER_INIT_SETTINGS.each do |setting|
142
+ define_method("#{setting}=") do |value|
143
+ warn_if_logger_initialized(setting)
144
+ instance_variable_set(:"@#{setting}", value)
145
+ end
146
+ end
147
+
148
+ POST_INIT_SETTINGS.each do |setting|
149
+ define_method("#{setting}=") do |value|
150
+ warn_if_fully_initialized(setting)
151
+ instance_variable_set(:"@#{setting}", value)
152
+ end
153
+ end
119
154
 
120
155
  # Setup default values
121
156
  def initialize
122
- @semantic = true
123
- @started = false
124
- @processing = false
125
- @rendered = false
126
- @ap_options = {multiline: false}
127
- @add_file_appender = true
128
- @quiet_assets = false
129
- @format = :default
130
- @named_tags = nil
131
- @filter = nil
132
- @console_logger = true
133
- @action_message_format = nil
134
- @replace_sidekiq_logger = true
157
+ @semantic = true
158
+ @started = false
159
+ @processing = false
160
+ @rendered = false
161
+ @ap_options = {multiline: false}
162
+ @add_file_appender = true
163
+ @quiet_assets = false
164
+ @format = :default
165
+ @filter = nil
166
+ @console_logger = true
167
+ @action_message_format = nil
168
+ @replace_sidekiq_logger = true
169
+ @replace_solid_queue_logger = true
170
+ end
171
+
172
+ # Declare the appenders for Rails Semantic Logger to create, replacing the
173
+ # default file appender:
174
+ #
175
+ # config.rails_semantic_logger.appenders do |appenders|
176
+ # appenders.add(file_name: "log/#{Rails.env}.log", formatter: :json)
177
+ # appenders.add_server(io: $stdout, formatter: :color)
178
+ # appenders.add_console(io: $stderr, formatter: :color)
179
+ # end
180
+ #
181
+ # The method names the context in which the appender is created; the destination
182
+ # is an ordinary `SemanticLogger.add_appender` argument. Use `add` for an
183
+ # appender that is always created, `add_server` for one created only when serving
184
+ # requests (`rails server`, Sidekiq in server mode; defaults to
185
+ # `$stdout`), and `add_console` for one created only inside a `rails console`
186
+ # session (defaults to `$stderr`). Any appender works in any context, so a
187
+ # context may declare several (e.g. a server-only stdout and file appender).
188
+ #
189
+ # `add_server` appenders are created automatically under `rails server` and
190
+ # Sidekiq in server mode. App servers without a first-party hook (bare puma,
191
+ # rackup, Passenger, Unicorn) are not detected; create them from the server's
192
+ # own boot hook instead, e.g. in `config/puma.rb`:
193
+ #
194
+ # on_booted { RailsSemanticLogger.add_server_appenders }
195
+ #
196
+ # Returns the underlying RailsSemanticLogger::Appenders collection. When at
197
+ # least one appender has been declared, the default file appender (and the
198
+ # `format`, `ap_options`, `filter`, and `add_file_appender` options) is no
199
+ # longer used.
200
+ def appenders
201
+ @appenders ||= RailsSemanticLogger::Appenders.new
202
+ if block_given?
203
+ warn_if_logger_initialized(:appenders)
204
+ yield @appenders
205
+ end
206
+ @appenders
207
+ end
208
+
209
+ # Whether the application declared its own appenders via #appenders.
210
+ def appenders?
211
+ defined?(@appenders) && @appenders.any?
212
+ end
213
+
214
+ # Marks that Rails Semantic Logger has already built its logger and the
215
+ # init-time appenders. Called by the engine at the end of the
216
+ # `:initialize_logger` initializer. After this point, settings that are only
217
+ # read while building those appenders no longer have any effect, so changing
218
+ # them emits a warning (see #warn_if_logger_initialized).
219
+ def logger_initialized!
220
+ @logger_initialized = true
221
+ end
222
+
223
+ # Marks that Rails has finished initializing the application (the engine's
224
+ # `config.after_initialize` has run). Called by the engine. After this point,
225
+ # the POST_INIT_SETTINGS have already been consumed, so changing them warns.
226
+ def fully_initialized!
227
+ @fully_initialized = true
228
+ end
229
+
230
+ def ap_options=(value)
231
+ deprecate_appender_option(:ap_options)
232
+ warn_if_logger_initialized(:ap_options)
233
+ @ap_options = value
234
+ end
235
+
236
+ def format=(value)
237
+ deprecate_appender_option(:format)
238
+ warn_if_logger_initialized(:format)
239
+ @format = value
240
+ end
241
+
242
+ def filter=(value)
243
+ deprecate_appender_option(:filter)
244
+ warn_if_logger_initialized(:filter)
245
+ @filter = value
246
+ end
247
+
248
+ def console_logger=(value)
249
+ deprecate_appender_option(:console_logger, via: "appenders.add_console(...)")
250
+ @console_logger = value
251
+ end
252
+
253
+ def add_file_appender=(value)
254
+ deprecate_appender_option(:add_file_appender)
255
+ warn_if_logger_initialized(:add_file_appender)
256
+ @add_file_appender = value
257
+ end
258
+
259
+ private
260
+
261
+ def deprecate_appender_option(option, via: "appenders.add(...)")
262
+ RailsSemanticLogger.deprecator.warn(
263
+ "`config.rails_semantic_logger.#{option}=` is deprecated and will be removed in a future release. " \
264
+ "Declare the destination and formatting directly instead, via " \
265
+ "`config.rails_semantic_logger.appenders { |appenders| #{via} }`."
266
+ )
267
+ end
268
+
269
+ # Warn when an init-time setting is changed after the logger and its
270
+ # appenders have already been built. This is the classic footgun of putting
271
+ # logger configuration in `config/initializers/*`, which Rails loads *after*
272
+ # the logger is initialized, so the change silently has no effect.
273
+ def warn_if_logger_initialized(setting)
274
+ return unless defined?(@logger_initialized) && @logger_initialized
275
+
276
+ RailsSemanticLogger.warn_initialized_too_late(setting)
277
+ end
278
+
279
+ # Warn when a setting consumed at the end of Rails initialization is changed
280
+ # after the application has already booted. Unlike #warn_if_logger_initialized,
281
+ # `config/initializers/*` is *not* too late for these, so it is not suggested.
282
+ def warn_if_fully_initialized(setting)
283
+ return unless defined?(@fully_initialized) && @fully_initialized
284
+
285
+ RailsSemanticLogger.warn_initialized_too_late(setting, config_initializers_too_late: false)
135
286
  end
136
287
  end
137
288
  end
@@ -31,23 +31,16 @@ module RailsSemanticLogger
31
31
 
32
32
  private
33
33
 
34
- @logger = SemanticLogger["Rack"]
34
+ @logger = SemanticLogger[::Rack]
35
35
  @started_request_log_level = :debug
36
36
 
37
37
  def call_app(request, env)
38
38
  instrumenter = ActiveSupport::Notifications.instrumenter
39
- if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
40
- handle = instrumenter.build_handle "request.action_dispatch", request: request
41
- instrumenter_finish = lambda {
42
- handle.finish
43
- }
44
- handle.start
45
- else
46
- instrumenter_state = instrumenter.start "request.action_dispatch", request: request
47
- instrumenter_finish = lambda {
48
- instrumenter.finish_with_state(instrumenter_state, "request.action_dispatch", request: request)
49
- }
50
- end
39
+ handle = instrumenter.build_handle "request.action_dispatch", request: request
40
+ instrumenter_finish = lambda {
41
+ handle.finish
42
+ }
43
+ handle.start
51
44
 
52
45
  logger.send(self.class.started_request_log_level) { started_request_message(request) }
53
46
  status, headers, body = @app.call(env)
@@ -2,38 +2,23 @@ module RailsSemanticLogger
2
2
  module Sidekiq
3
3
  module Defaults
4
4
  # Prevent exception logging during standard error handling since the Job Logger below already logs the exception.
5
+ # Logs the remaining Sidekiq context at :info (matching upstream Sidekiq's default handler) rather than :warn,
6
+ # since the exception itself is already logged at :error by the Job Logger.
7
+ # Sidekiq 7.1.6+ calls error handlers with a third config argument; earlier 7.x versions pass only two.
5
8
  ERROR_HANDLER =
6
- if ::Sidekiq::VERSION.to_f < 7.1 ||
7
- (::Sidekiq::VERSION.to_f == 7.1 && ::Sidekiq::VERSION.split(".").last.to_i < 6)
8
- lambda do |_ex, ctx|
9
- unless ctx.empty?
10
- job_hash = ctx[:job] || {}
11
- klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
12
- logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
13
- ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
14
- end
15
- end
16
- else
17
- lambda do |_ex, ctx, _default_configuration|
18
- unless ctx.empty?
19
- job_hash = ctx[:job] || {}
20
- klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
21
- logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
22
- ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
23
- end
9
+ lambda do |_ex, ctx, _config = nil|
10
+ unless ctx.empty?
11
+ job_hash = ctx[:job] || {}
12
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
13
+ logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
14
+ ctx[:context] ? logger.info(ctx[:context], ctx) : logger.info(ctx)
24
15
  end
25
16
  end
26
17
 
27
- # Returns the default logger after removing from the supplied list.
28
- # Returns [nil] when the default logger was not present.
18
+ # Returns the default error handler after removing it from the supplied list.
19
+ # Returns [nil] when the default handler was not present.
29
20
  def self.delete_default_error_handler(error_handlers)
30
- return error_handlers.delete(::Sidekiq::Config::ERROR_HANDLER) if defined?(::Sidekiq::Config::ERROR_HANDLER)
31
- return error_handlers.delete(::Sidekiq::DEFAULT_ERROR_HANDLER) if defined?(::Sidekiq::DEFAULT_ERROR_HANDLER)
32
-
33
- return unless defined?(::Sidekiq::ExceptionHandler)
34
-
35
- existing = error_handlers.find { |handler| handler.is_a?(::Sidekiq::ExceptionHandler::Logger) }
36
- error_handlers.delete(existing) if existing
21
+ error_handlers.delete(::Sidekiq::Config::ERROR_HANDLER)
37
22
  end
38
23
  end
39
24
  end
@@ -1,6 +1,10 @@
1
1
  module RailsSemanticLogger
2
2
  module Sidekiq
3
3
  class JobLogger
4
+ # Job attributes copied into the logging context, matching Sidekiq's own defaults.
5
+ # Sidekiq 8 makes this configurable via `config[:logged_job_attributes]`.
6
+ DEFAULT_LOGGED_JOB_ATTRIBUTES = %w[bid tags].freeze
7
+
4
8
  class << self
5
9
  attr_writer :perform_messages
6
10
 
@@ -9,8 +13,9 @@ module RailsSemanticLogger
9
13
  end
10
14
  end
11
15
 
12
- # Sidekiq 6.5 does not take any arguments, whereas v7 is given a logger
13
- def initialize(*_args)
16
+ # Sidekiq 7 passes its logger, whereas Sidekiq 8 passes its config.
17
+ def initialize(arg = nil)
18
+ @config = arg if arg.respond_to?(:[])
14
19
  end
15
20
 
16
21
  def call(item, queue, &block)
@@ -19,7 +24,7 @@ module RailsSemanticLogger
19
24
 
20
25
  SemanticLogger.tagged(queue: queue) do
21
26
  if perform_messages_enabled?
22
- # Latency is the time between when the job was enqueued and when it started executing.
27
+ # Latency is the time between when the job was enqueued and when it started executing.
23
28
  logger.info(
24
29
  "Start #perform",
25
30
  metric: "sidekiq.queue.latency",
@@ -36,8 +41,8 @@ module RailsSemanticLogger
36
41
  metric: "sidekiq.job.perform",
37
42
  &block
38
43
  )
39
- else
40
- yield if block_given?
44
+ elsif block_given?
45
+ yield
41
46
  end
42
47
  end
43
48
  end
@@ -56,21 +61,36 @@ module RailsSemanticLogger
56
61
  private
57
62
 
58
63
  def perform_messages_enabled?
64
+ return false if @config && @config[:skip_default_job_logging]
65
+
59
66
  self.class.perform_messages != false
60
67
  end
61
68
 
62
69
  def job_hash_context(job_hash)
63
- h = {jid: job_hash["jid"]}
64
- h[:bid] = job_hash["bid"] if job_hash["bid"]
65
- h[:tags] = job_hash["tags"] if job_hash["tags"]
70
+ h = {jid: job_hash["jid"], class: job_hash["wrapped"] || job_hash["class"]}
71
+ logged_job_attributes.each do |attr|
72
+ h[attr.to_sym] = job_hash[attr] if job_hash.key?(attr)
73
+ end
66
74
  h[:queue] = job_hash["queue"] if job_hash["queue"]
67
75
  h
68
76
  end
69
77
 
78
+ def logged_job_attributes
79
+ (@config && @config[:logged_job_attributes]) || DEFAULT_LOGGED_JOB_ATTRIBUTES
80
+ end
81
+
70
82
  def job_latency_ms(job)
71
83
  return unless job && job["enqueued_at"]
72
84
 
73
- (Time.now.to_f - job["enqueued_at"].to_f) * 1000
85
+ enqueued_at = job["enqueued_at"]
86
+ if enqueued_at.is_a?(Float)
87
+ # Sidekiq <= 7: seconds since epoch
88
+ (Time.now.to_f - enqueued_at) * 1000
89
+ else
90
+ # Sidekiq 8+: milliseconds since epoch
91
+ now = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
92
+ now - enqueued_at
93
+ end
74
94
  end
75
95
  end
76
96
  end