rails_semantic_logger 4.20.0 → 5.0.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 +7 -4
  4. data/lib/rails_semantic_logger/action_controller/log_subscriber.rb +86 -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 +74 -40
  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 +91 -0
  10. data/lib/rails_semantic_logger/engine.rb +47 -36
  11. data/lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb +44 -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/extensions/sidekiq/sidekiq.rb +4 -4
  18. data/lib/rails_semantic_logger/options.rb +171 -20
  19. data/lib/rails_semantic_logger/rack/logger.rb +6 -13
  20. data/lib/rails_semantic_logger/sidekiq/defaults.rb +4 -2
  21. data/lib/rails_semantic_logger/sidekiq/job_logger.rb +13 -5
  22. data/lib/rails_semantic_logger/solid_queue/log_subscriber.rb +179 -0
  23. data/lib/rails_semantic_logger/version.rb +1 -1
  24. data/lib/rails_semantic_logger.rb +81 -26
  25. metadata +15 -21
  26. data/lib/rails_semantic_logger/delayed_job/plugin.rb +0 -11
  27. data/lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb +0 -13
  28. data/lib/rails_semantic_logger/extensions/rack/server.rb +0 -12
  29. data/lib/rails_semantic_logger/extensions/rackup/server.rb +0 -12
@@ -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
@@ -12,12 +12,12 @@ module Sidekiq
12
12
  if defined?(::Sidekiq::Logging)
13
13
  # Replace Sidekiq logging context
14
14
  module Logging
15
- def self.with_context(msg, &block)
16
- SemanticLogger.tagged(msg, &block)
15
+ def self.with_context(msg, &)
16
+ SemanticLogger.tagged(msg, &)
17
17
  end
18
18
 
19
19
  def self.job_hash_context(job_hash)
20
- h = {jid: job_hash["jid"]}
20
+ h = {jid: job_hash["jid"], class: job_hash["wrapped"] || job_hash["class"]}
21
21
  h[:bid] = job_hash["bid"] if job_hash["bid"]
22
22
  h[:queue] = job_hash["queue"] if job_hash["queue"]
23
23
  h
@@ -30,7 +30,7 @@ module Sidekiq
30
30
  # Convert string to machine readable format
31
31
  class Processor
32
32
  def log_context(job_hash)
33
- h = {jid: job_hash["jid"]}
33
+ h = {jid: job_hash["jid"], class: job_hash["wrapped"] || job_hash["class"]}
34
34
  h[:bid] = job_hash["bid"] if job_hash["bid"]
35
35
  h[:queue] = job_hash["queue"] if job_hash["queue"]
36
36
  h
@@ -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`, a rack 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,6 +2,8 @@ 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.
5
7
  ERROR_HANDLER =
6
8
  if ::Sidekiq::VERSION.to_f < 7.1 ||
7
9
  (::Sidekiq::VERSION.to_f == 7.1 && ::Sidekiq::VERSION.split(".").last.to_i < 6)
@@ -10,7 +12,7 @@ module RailsSemanticLogger
10
12
  job_hash = ctx[:job] || {}
11
13
  klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
12
14
  logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
13
- ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
15
+ ctx[:context] ? logger.info(ctx[:context], ctx) : logger.info(ctx)
14
16
  end
15
17
  end
16
18
  else
@@ -19,7 +21,7 @@ module RailsSemanticLogger
19
21
  job_hash = ctx[:job] || {}
20
22
  klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
21
23
  logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
22
- ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
24
+ ctx[:context] ? logger.info(ctx[:context], ctx) : logger.info(ctx)
23
25
  end
24
26
  end
25
27
  end
@@ -19,7 +19,7 @@ module RailsSemanticLogger
19
19
 
20
20
  SemanticLogger.tagged(queue: queue) do
21
21
  if perform_messages_enabled?
22
- # Latency is the time between when the job was enqueued and when it started executing.
22
+ # Latency is the time between when the job was enqueued and when it started executing.
23
23
  logger.info(
24
24
  "Start #perform",
25
25
  metric: "sidekiq.queue.latency",
@@ -36,8 +36,8 @@ module RailsSemanticLogger
36
36
  metric: "sidekiq.job.perform",
37
37
  &block
38
38
  )
39
- else
40
- yield if block_given?
39
+ elsif block_given?
40
+ yield
41
41
  end
42
42
  end
43
43
  end
@@ -60,7 +60,7 @@ module RailsSemanticLogger
60
60
  end
61
61
 
62
62
  def job_hash_context(job_hash)
63
- h = {jid: job_hash["jid"]}
63
+ h = {jid: job_hash["jid"], class: job_hash["wrapped"] || job_hash["class"]}
64
64
  h[:bid] = job_hash["bid"] if job_hash["bid"]
65
65
  h[:tags] = job_hash["tags"] if job_hash["tags"]
66
66
  h[:queue] = job_hash["queue"] if job_hash["queue"]
@@ -70,7 +70,15 @@ module RailsSemanticLogger
70
70
  def job_latency_ms(job)
71
71
  return unless job && job["enqueued_at"]
72
72
 
73
- (Time.now.to_f - job["enqueued_at"].to_f) * 1000
73
+ enqueued_at = job["enqueued_at"]
74
+ if enqueued_at.is_a?(Float)
75
+ # Sidekiq <= 7: seconds since epoch
76
+ (Time.now.to_f - enqueued_at) * 1000
77
+ else
78
+ # Sidekiq 8+: milliseconds since epoch
79
+ now = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
80
+ now - enqueued_at
81
+ end
74
82
  end
75
83
  end
76
84
  end
@@ -0,0 +1,179 @@
1
+ module RailsSemanticLogger
2
+ module SolidQueue
3
+ class LogSubscriber < ::ActiveSupport::LogSubscriber
4
+ def dispatch_scheduled(event)
5
+ log_event(event, :debug, "Dispatch scheduled jobs", **event.payload.slice(:batch_size, :size))
6
+ end
7
+
8
+ def claim(event)
9
+ log_event(event, :debug, "Claim jobs", **event.payload.slice(:process_id, :job_ids, :claimed_job_ids, :size))
10
+ end
11
+
12
+ def release_many_claimed(event)
13
+ log_event(event, :info, "Release claimed jobs", **event.payload.slice(:size))
14
+ end
15
+
16
+ def fail_many_claimed(event)
17
+ log_event(event, :warn, "Fail claimed jobs", **event.payload.slice(:job_ids, :process_ids))
18
+ end
19
+
20
+ def release_claimed(event)
21
+ log_event(event, :info, "Release claimed job", **event.payload.slice(:job_id, :process_id))
22
+ end
23
+
24
+ def retry_all(event)
25
+ log_event(event, :debug, "Retry failed jobs", **event.payload.slice(:jobs_size, :size))
26
+ end
27
+
28
+ def retry(event)
29
+ log_event(event, :debug, "Retry failed job", **event.payload.slice(:job_id))
30
+ end
31
+
32
+ def discard_all(event)
33
+ log_event(event, :debug, "Discard jobs", **event.payload.slice(:jobs_size, :size, :status))
34
+ end
35
+
36
+ def discard(event)
37
+ log_event(event, :debug, "Discard job", **event.payload.slice(:job_id, :status))
38
+ end
39
+
40
+ def release_many_blocked(event)
41
+ log_event(event, :debug, "Unblock jobs", **event.payload.slice(:limit, :size))
42
+ end
43
+
44
+ def release_blocked(event)
45
+ log_event(event, :debug, "Release blocked job", **event.payload.slice(:job_id, :concurrency_key, :released))
46
+ end
47
+
48
+ def enqueue_recurring_task(event)
49
+ attributes = event.payload.slice(:task, :active_job_id, :enqueue_error)
50
+ attributes[:at] = event.payload[:at]&.iso8601
51
+
52
+ if attributes[:active_job_id].nil? && event.payload[:skipped].nil?
53
+ log_event(event, :error, "Error enqueuing recurring task", **attributes)
54
+ elsif event.payload[:other_adapter]
55
+ log_event(event, :debug, "Enqueued recurring task outside Solid Queue", **attributes)
56
+ else
57
+ action = event.payload[:skipped].present? ? "Skipped recurring task – already dispatched" : "Enqueued recurring task"
58
+ log_event(event, :debug, action, **attributes)
59
+ end
60
+ end
61
+
62
+ def start_process(event)
63
+ process = event.payload[:process]
64
+ attributes = process_attributes(process).merge(process.metadata)
65
+
66
+ log_event(event, :info, "Started #{process.kind}", **attributes)
67
+ end
68
+
69
+ def shutdown_process(event)
70
+ process = event.payload[:process]
71
+ attributes = process_attributes(process).merge(process.metadata)
72
+
73
+ log_event(event, :info, "Shutdown #{process.kind}", **attributes)
74
+ end
75
+
76
+ def register_process(event)
77
+ process_kind = event.payload[:kind]
78
+ attributes = event.payload.slice(:pid, :hostname, :process_id, :name)
79
+
80
+ if (error = event.payload[:error])
81
+ log_event(event, :warn, "Error registering #{process_kind}", exception: error, **attributes)
82
+ else
83
+ log_event(event, :debug, "Register #{process_kind}", **attributes)
84
+ end
85
+ end
86
+
87
+ def deregister_process(event)
88
+ process = event.payload[:process]
89
+ attributes = {
90
+ process_id: process.id,
91
+ pid: process.pid,
92
+ hostname: process.hostname,
93
+ name: process.name,
94
+ last_heartbeat_at: process.last_heartbeat_at.iso8601,
95
+ claimed_size: event.payload[:claimed_size],
96
+ pruned: event.payload[:pruned]
97
+ }
98
+
99
+ if (error = event.payload[:error])
100
+ log_event(event, :warn, "Error deregistering #{process.kind}", exception: error, **attributes)
101
+ else
102
+ log_event(event, :debug, "Deregister #{process.kind}", **attributes)
103
+ end
104
+ end
105
+
106
+ def prune_processes(event)
107
+ log_event(event, :debug, "Prune dead processes", **event.payload.slice(:size))
108
+ end
109
+
110
+ def thread_error(event)
111
+ log_event(event, :error, "Error in thread", exception: event.payload[:error])
112
+ end
113
+
114
+ def graceful_termination(event)
115
+ attributes = event.payload.slice(:process_id, :supervisor_pid, :supervised_processes)
116
+
117
+ if event.payload[:shutdown_timeout_exceeded]
118
+ log_event(event, :warn, "Supervisor wasn't terminated gracefully - shutdown timeout exceeded", **attributes)
119
+ else
120
+ log_event(event, :info, "Supervisor terminated gracefully", **attributes)
121
+ end
122
+ end
123
+
124
+ def immediate_termination(event)
125
+ log_event(event, :info, "Supervisor terminated immediately",
126
+ **event.payload.slice(:process_id, :supervisor_pid, :supervised_processes))
127
+ end
128
+
129
+ def unhandled_signal_error(event)
130
+ log_event(event, :error, "Received unhandled signal", **event.payload.slice(:signal))
131
+ end
132
+
133
+ def replace_fork(event)
134
+ supervisor_pid = event.payload[:supervisor_pid]
135
+ status = event.payload[:status]
136
+ attributes = event.payload.slice(:pid).merge(
137
+ status: status.exitstatus || "no exit status set",
138
+ pid_from_status: status.pid,
139
+ signaled: status.signaled?,
140
+ stopsig: status.stopsig,
141
+ termsig: status.termsig
142
+ )
143
+
144
+ if (replaced_fork = event.payload[:fork])
145
+ log_event(event, :info, "Replaced terminated #{replaced_fork.kind}",
146
+ **attributes, hostname: replaced_fork.hostname, name: replaced_fork.name)
147
+ elsif supervisor_pid != 1 # Running Docker, possibly having some processes that have been reparented
148
+ log_event(event, :warn, "Tried to replace forked process but it had already died", **attributes)
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def process_attributes(process)
155
+ {
156
+ pid: process.pid,
157
+ hostname: process.hostname,
158
+ process_id: process.process_id,
159
+ name: process.name
160
+ }
161
+ end
162
+
163
+ def log_event(event, level, action, exception: nil, **attributes)
164
+ logger.public_send(level) do
165
+ msg = {message: action, payload: attributes, duration: event.duration}
166
+ msg[:exception] = exception if exception
167
+ # Emit a metric for every info/warn/error entry, named after the notification
168
+ # (e.g. "start_process.solid_queue" -> "rails.solid_queue.start_process"). Debug entries are excluded.
169
+ msg[:metric] = "rails.solid_queue.#{event.name.split('.').first}" unless level == :debug
170
+ msg
171
+ end
172
+ end
173
+
174
+ def logger
175
+ @logger ||= SemanticLogger[::SolidQueue]
176
+ end
177
+ end
178
+ end
179
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsSemanticLogger
2
- VERSION = "4.20.0".freeze
2
+ VERSION = "5.0.0".freeze
3
3
  end