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.
- checksums.yaml +4 -4
- data/README.md +55 -98
- data/Rakefile +7 -4
- data/lib/rails_semantic_logger/action_controller/log_subscriber.rb +86 -16
- data/lib/rails_semantic_logger/action_mailer/log_subscriber.rb +36 -22
- data/lib/rails_semantic_logger/action_view/log_subscriber.rb +74 -40
- data/lib/rails_semantic_logger/active_job/log_subscriber.rb +216 -7
- data/lib/rails_semantic_logger/active_record/log_subscriber.rb +62 -160
- data/lib/rails_semantic_logger/appenders.rb +91 -0
- data/lib/rails_semantic_logger/engine.rb +47 -36
- data/lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb +44 -3
- data/lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb +5 -14
- data/lib/rails_semantic_logger/extensions/active_job/logging.rb +2 -2
- data/lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb +2 -2
- data/lib/rails_semantic_logger/extensions/active_support/logger.rb +24 -15
- data/lib/rails_semantic_logger/extensions/rails/server.rb +1 -1
- data/lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb +4 -4
- data/lib/rails_semantic_logger/options.rb +171 -20
- data/lib/rails_semantic_logger/rack/logger.rb +6 -13
- data/lib/rails_semantic_logger/sidekiq/defaults.rb +4 -2
- data/lib/rails_semantic_logger/sidekiq/job_logger.rb +13 -5
- data/lib/rails_semantic_logger/solid_queue/log_subscriber.rb +179 -0
- data/lib/rails_semantic_logger/version.rb +1 -1
- data/lib/rails_semantic_logger.rb +81 -26
- metadata +15 -21
- data/lib/rails_semantic_logger/delayed_job/plugin.rb +0 -11
- data/lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb +0 -13
- data/lib/rails_semantic_logger/extensions/rack/server.rb +0 -12
- 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
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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, &
|
|
16
|
-
SemanticLogger.tagged(msg, &
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
123
|
-
@started
|
|
124
|
-
@processing
|
|
125
|
-
@rendered
|
|
126
|
-
@ap_options
|
|
127
|
-
@add_file_appender
|
|
128
|
-
@quiet_assets
|
|
129
|
-
@format
|
|
130
|
-
@
|
|
131
|
-
@
|
|
132
|
-
@
|
|
133
|
-
@
|
|
134
|
-
@
|
|
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[
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
yield
|
|
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
|
-
|
|
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
|