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,42 +1,61 @@
1
+ # This subscriber is a reimplementation of Rails' own ActiveRecord::LogSubscriber that emits
2
+ # structured (message + payload) log entries instead of formatted text. When Rails changes its
3
+ # subscriber, those changes must be brought across here. Compare against the upstream source for
4
+ # each supported Rails version:
5
+ #
6
+ # Rails 8.1: https://github.com/rails/rails/blob/8-1-stable/activerecord/lib/active_record/log_subscriber.rb
7
+ # Rails 8.0: https://github.com/rails/rails/blob/8-0-stable/activerecord/lib/active_record/log_subscriber.rb
8
+ # Rails 7.2: https://github.com/rails/rails/blob/7-2-stable/activerecord/lib/active_record/log_subscriber.rb
9
+ #
10
+ # The upstream subscriber is functionally identical across Rails 7.2, 8.0, and 8.1 for everything
11
+ # that affects structured output. The only differences between those versions are in the internal
12
+ # `query_source_location` helper (used by `verbose_query_logs` to print the "↳ source" line) and a
13
+ # `:nodoc:` comment, neither of which changes the event payload. As a result no version-specific
14
+ # behavior is required here.
15
+ #
16
+ # As of Rails 8.1 there is also a parallel ActiveRecord::StructuredEventSubscriber (it emits
17
+ # structured events to Rails.event rather than text to Rails.logger). It is the authoritative,
18
+ # Rails-maintained reference for field names and payload shape; the fields below (e.g. :lock_wait
19
+ # and the strict_loading_violation payload) follow it. We do not use it (see CLAUDE.md), but diff
20
+ # against it when adding fields:
21
+ #
22
+ # Rails 8.1: https://github.com/rails/rails/blob/8-1-stable/activerecord/lib/active_record/structured_event_subscriber.rb
1
23
  module RailsSemanticLogger
2
24
  module ActiveRecord
3
25
  class LogSubscriber < ActiveSupport::LogSubscriber
4
26
  IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
5
27
 
6
- class << self
7
- attr_reader :logger
8
- end
28
+ def strict_loading_violation(event)
29
+ return unless logger.debug?
9
30
 
10
- # Rails 7.1 stopped using runtime in log subscribers
11
- if Rails.version.to_f < 7.1
12
- def self.runtime=(value)
13
- ::ActiveRecord::RuntimeRegistry.sql_runtime = value
14
- end
31
+ payload = event.payload
32
+ owner = payload[:owner]
33
+ reflection = payload[:reflection]
15
34
 
16
- def self.runtime
17
- ::ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
18
- end
35
+ log_payload = {owner: owner.name, association: reflection.name}
36
+ log_payload[:class] = reflection.klass.name unless reflection.polymorphic?
19
37
 
20
- def self.reset_runtime
21
- rt = runtime
22
- self.runtime = 0
23
- rt
24
- end
38
+ logger.debug(
39
+ message: reflection.strict_loading_violation_message(owner),
40
+ payload: log_payload
41
+ )
25
42
  end
26
43
 
27
44
  def sql(event)
28
- self.class.runtime += event.duration if self.class.respond_to?(:runtime)
29
45
  return unless logger.debug?
30
46
 
31
47
  payload = event.payload
32
48
  name = payload[:name]
33
49
  return if IGNORE_PAYLOAD_NAMES.include?(name)
34
50
 
35
- log_payload = {sql: payload[:sql]}
36
- log_payload[:binds] = bind_values(payload) unless (payload[:binds] || []).empty?
37
- log_payload[:allocations] = event.allocations if event.respond_to?(:allocations)
38
- log_payload[:cached] = event.payload[:cached]
39
- log_payload[:async] = true if event.payload[:async]
51
+ log_payload = {sql: payload[:sql]}
52
+ log_payload[:binds] = bind_values(payload) unless (payload[:binds] || []).empty?
53
+ log_payload[:allocations] = event.allocations
54
+ log_payload[:cached] = true if payload[:cached]
55
+ if payload[:async]
56
+ log_payload[:async] = true
57
+ log_payload[:lock_wait] = payload[:lock_wait] if payload[:lock_wait]
58
+ end
40
59
 
41
60
  log = {
42
61
  message: name,
@@ -54,137 +73,38 @@ module RailsSemanticLogger
54
73
 
55
74
  private
56
75
 
57
- @logger = SemanticLogger["ActiveRecord"]
58
-
59
76
  # When multiple values are received for a single bound field, it is converted into an array
60
77
  def add_bind_value(binds, key, value)
61
78
  key = key.downcase.to_sym unless key.nil?
62
79
 
63
- if rails_filter_params_include?(key)
64
- value = "[FILTERED]"
65
- elsif binds.key?(key)
66
- value = (Array(binds[key]) << value)
67
- end
68
-
80
+ value = (Array(binds[key]) << value) if binds.key?(key)
69
81
  binds[key] = value
70
82
  end
71
83
 
72
- def rails_filter_params_include?(key)
73
- filter_parameters = Rails.configuration.filter_parameters
74
-
75
- return filter_parameters.first.match? key if filter_parameters.first.is_a? Regexp
76
-
77
- filter_parameters.include? key
78
- end
79
-
80
84
  def logger
81
- self.class.logger
82
- end
83
-
84
- #
85
- # Rails 3,4,5 hell trying to get the bind values
86
- #
87
-
88
- def bind_values_v3(payload)
89
- binds = {}
90
- payload[:binds].each do |col, v|
91
- if col
92
- add_bind_value(binds, col.name, v)
93
- else
94
- binds[nil] = v
95
- end
96
- end
97
- binds
98
- end
99
-
100
- def bind_values_v4(payload)
101
- binds = {}
102
- payload[:binds].each do |col, v|
103
- attr_name, value = render_bind(col, v)
104
- add_bind_value(binds, attr_name, value)
105
- end
106
- binds
107
- end
108
-
109
- def bind_values_v5_0_0(payload)
110
- binds = {}
111
- payload[:binds].each do |attr|
112
- attr_name, value = render_bind(attr)
113
- add_bind_value(binds, attr_name, value)
114
- end
115
- binds
116
- end
117
-
118
- def bind_values_v5_0_3(payload)
119
- binds = {}
120
- casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
121
- payload[:binds].zip(casted_params).map do |attr, value|
122
- attr_name, value = render_bind(attr, value)
123
- add_bind_value(binds, attr_name, value)
124
- end
125
- binds
126
- end
127
-
128
- def bind_values_v5_1_5(payload)
129
- binds = {}
130
- casted_params = type_casted_binds(payload[:type_casted_binds])
131
- payload[:binds].zip(casted_params).map do |attr, value|
132
- attr_name, value = render_bind(attr, value)
133
- add_bind_value(binds, attr_name, value)
134
- end
135
- binds
85
+ ::ActiveRecord::Base.logger
136
86
  end
137
87
 
138
- def bind_values_v6_1(payload)
88
+ def bind_values(payload)
139
89
  binds = {}
140
90
  casted_params = type_casted_binds(payload[:type_casted_binds])
141
91
  payload[:binds].each_with_index do |attr, i|
142
- attr_name, value = render_bind(attr, casted_params[i])
92
+ filtered_value = filter(attribute_name(attr, i), casted_params[i])
93
+ attr_name, value = render_bind(attr, filtered_value)
143
94
  add_bind_value(binds, attr_name, value)
144
95
  end
145
96
  binds
146
97
  end
147
98
 
148
- def render_bind_v4_2(column, value)
149
- if column
150
- if column.binary?
151
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
152
- value = value[:value] if value.is_a?(Hash)
153
- value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
154
- end
155
-
156
- [column.name, value]
157
- else
158
- [nil, value]
99
+ def attribute_name(attr, index)
100
+ if attr.respond_to?(:name)
101
+ attr.name
102
+ elsif attr.respond_to?(:[]) && attr[index].respond_to?(:name)
103
+ attr[index].name
159
104
  end
160
105
  end
161
106
 
162
- def render_bind_v5_0_0(attribute)
163
- value =
164
- if attribute.type.binary? && attribute.value
165
- if attribute.value.is_a?(Hash)
166
- "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
167
- else
168
- "<#{attribute.value.bytesize} bytes of binary data>"
169
- end
170
- else
171
- attribute.value_for_database
172
- end
173
-
174
- [attribute.name, value]
175
- end
176
-
177
- def render_bind_v5_0_3(attr, value)
178
- if attr.is_a?(Array)
179
- attr = attr.first
180
- elsif attr.type.binary? && attr.value
181
- value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
182
- end
183
-
184
- [attr&.name, value]
185
- end
186
-
187
- def render_bind_v6_1(attr, value)
107
+ def render_bind(attr, value)
188
108
  case attr
189
109
  when ActiveModel::Attribute
190
110
  value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" if attr.type.binary? && attr.value
@@ -197,37 +117,19 @@ module RailsSemanticLogger
197
117
  [attr&.name || :nil, value]
198
118
  end
199
119
 
200
- def type_casted_binds_v5_0_3(binds, casted_binds)
201
- casted_binds || ::ActiveRecord::Base.connection.type_casted_binds(binds)
202
- end
120
+ # Filter sensitive bind values using the same parameter filter Rails uses for ActiveRecord,
121
+ # which is derived from ActiveRecord::Base.filter_attributes (config.filter_parameters).
122
+ def filter(name, value)
123
+ return value if name.nil?
203
124
 
204
- def type_casted_binds_v5_1_5(casted_binds)
205
- casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
125
+ filtered = ::ActiveRecord::Base.inspection_filter.filter_param(name.to_s, value)
126
+ # filter_param returns the same object when nothing was filtered, otherwise a mask object
127
+ # (a String delegate). Coerce the mask to a plain String for clean structured output.
128
+ filtered.equal?(value) ? value : filtered.to_s
206
129
  end
207
130
 
208
- if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 2 # 5.0.0 - 5.0.2
209
- alias bind_values bind_values_v5_0_0
210
- alias render_bind render_bind_v5_0_0
211
- elsif Rails::VERSION::MAJOR == 5 &&
212
- ((Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 6) ||
213
- (Rails::VERSION::MINOR == 1 && Rails::VERSION::TINY <= 4)) # 5.0.3 - 5.0.6 && 5.1.0 - 5.1.4
214
- alias bind_values bind_values_v5_0_3
215
- alias render_bind render_bind_v5_0_3
216
- alias type_casted_binds type_casted_binds_v5_0_3
217
- elsif (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR > 0) ||
218
- Rails::VERSION::MAJOR >= 7 # ~> 6.1.0 && >= 7.x.x
219
- alias bind_values bind_values_v6_1
220
- alias render_bind render_bind_v6_1
221
- alias type_casted_binds type_casted_binds_v5_1_5
222
- elsif Rails::VERSION::MAJOR >= 5 # ~> 5.1.5 && ~> 5.0.7 && 6.x.x
223
- alias bind_values bind_values_v5_1_5
224
- alias render_bind render_bind_v5_0_3
225
- alias type_casted_binds type_casted_binds_v5_1_5
226
- elsif Rails.version.to_i >= 4 # 4.x
227
- alias bind_values bind_values_v4
228
- alias render_bind render_bind_v4_2
229
- else # 3.x
230
- alias bind_values bind_values_v3
131
+ def type_casted_binds(casted_binds)
132
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
231
133
  end
232
134
  end
233
135
  end
@@ -0,0 +1,91 @@
1
+ module RailsSemanticLogger
2
+ # Collects the appenders declared by the application via:
3
+ #
4
+ # config.rails_semantic_logger.appenders do |appenders|
5
+ # appenders.add(file_name: "log/#{Rails.env}.log", formatter: :json)
6
+ # appenders.add_server(io: $stdout, formatter: :color)
7
+ # appenders.add_console(io: $stderr, formatter: :color)
8
+ # end
9
+ #
10
+ # When at least one appender is declared this way, Rails Semantic Logger stops
11
+ # building its own default file appender (`format`, `ap_options`, `filter`, and
12
+ # `add_file_appender` no longer apply) and instead creates exactly the appenders
13
+ # declared here.
14
+ #
15
+ # The methods name the *context* in which the appender is created; the
16
+ # destination and formatting are ordinary `SemanticLogger.add_appender` arguments:
17
+ #
18
+ # #add - always created, during Rails initialization.
19
+ # #add_server - created only when serving requests (`rails server`, a rack
20
+ # server, Sidekiq in server mode). Defaults to `$stdout`.
21
+ # #add_console - created only inside a `rails console` session. Defaults to
22
+ # `$stderr` so log output does not tangle with command results.
23
+ #
24
+ # Because each call appends to its context, any appender works in any context,
25
+ # and a context can have several (e.g. a server-only stdout *and* file appender).
26
+ class Appenders
27
+ include Enumerable
28
+
29
+ # Destination keys understood by SemanticLogger.add_appender. When none is
30
+ # supplied to #add_server / #add_console, the context's default stream is used.
31
+ DESTINATIONS = %i[io file_name appender logger metric].freeze
32
+
33
+ def initialize
34
+ @definitions = []
35
+ @server = []
36
+ @console = []
37
+ end
38
+
39
+ # Declare an appender. Accepts the same arguments (and optional block) as
40
+ # SemanticLogger.add_appender. Returns self so calls can be chained.
41
+ def add(**args, &block)
42
+ @definitions << [args, block]
43
+ self
44
+ end
45
+
46
+ # Declare an appender that is only created when the application is serving
47
+ # requests: `rails server`, a rack server started directly (puma, etc.), or
48
+ # Sidekiq in server mode. It is never created during a non-serving boot (rake
49
+ # tasks, runners, generators), so it only appears where it is useful.
50
+ #
51
+ # Accepts the same arguments (and optional block) as SemanticLogger.add_appender.
52
+ # When no destination is given it defaults to `$stdout`; the formatter defaults
53
+ # to `:color`.
54
+ def add_server(**args, &block)
55
+ @server << [defaults(args, io: $stdout), block]
56
+ self
57
+ end
58
+
59
+ # Declare an appender that is only created when running inside a `rails console`
60
+ # session. Identical to #add_server except it defaults to `$stderr`.
61
+ #
62
+ # Accepts the same arguments (and optional block) as SemanticLogger.add_appender.
63
+ def add_console(**args, &block)
64
+ @console << [defaults(args, io: $stderr), block]
65
+ self
66
+ end
67
+
68
+ # The appenders declared via #add_server / #add_console, each as
69
+ # [Hash args, Proc|nil block].
70
+ attr_reader :server, :console
71
+
72
+ # Yields each #add declaration as [Hash args, Proc|nil block].
73
+ def each(&)
74
+ @definitions.each(&)
75
+ end
76
+
77
+ def any?
78
+ @definitions.any? || @server.any? || @console.any?
79
+ end
80
+
81
+ private
82
+
83
+ # Apply the context default stream and formatter, without overriding a
84
+ # destination (io/file_name/...) the caller already supplied.
85
+ def defaults(args, io:)
86
+ args = {formatter: :color}.merge(args)
87
+ args[:io] = io unless DESTINATIONS.any? { |key| args.key?(key) }
88
+ args
89
+ end
90
+ end
91
+ end
@@ -42,7 +42,11 @@ module RailsSemanticLogger
42
42
  # own file loggers which would result in duplicate logging to the same log file
43
43
  Rails.logger = config.logger =
44
44
  begin
45
- if config.rails_semantic_logger.add_file_appender
45
+ if config.rails_semantic_logger.appenders?
46
+ # The application declared its own appenders; create those and skip the default file appender.
47
+ RailsSemanticLogger.add_appenders(config.rails_semantic_logger.appenders)
48
+ elsif config.rails_semantic_logger.add_file_appender
49
+ # DEPRECATED: This option is deprecated and will be removed in a future version.
46
50
  path = config.paths["log"].first
47
51
  FileUtils.mkdir_p(File.dirname(path)) unless File.exist?(File.dirname(path))
48
52
 
@@ -83,11 +87,15 @@ module RailsSemanticLogger
83
87
  logger
84
88
  end
85
89
 
90
+ # Logger and init-time appenders are now built; settings read above no
91
+ # longer take effect, so flag late changes (e.g. from config/initializers).
92
+ config.rails_semantic_logger.logger_initialized!
93
+
86
94
  # Replace Rails loggers
87
95
  %i[active_record action_controller action_mailer action_view].each do |name|
88
96
  ActiveSupport.on_load(name) { include SemanticLogger::Loggable }
89
97
  end
90
- ActiveSupport.on_load(:action_cable) { self.logger = SemanticLogger["ActionCable"] }
98
+ ActiveSupport.on_load(:action_cable) { self.logger = SemanticLogger[::ActionCable] }
91
99
  end
92
100
 
93
101
  # Before any initializers run, but after the gems have been loaded
@@ -105,7 +113,7 @@ module RailsSemanticLogger
105
113
  Mongo::Logger.logger = SemanticLogger[Mongo] if defined?(Mongo::Logger)
106
114
 
107
115
  # Replace the Resque Logger
108
- Resque.logger = SemanticLogger[Resque] if defined?(Resque) && Resque.respond_to?(:logger=)
116
+ Resque.logger = SemanticLogger[Resque] if defined?(Resque) && Resque.respond_to?(:logger=)
109
117
 
110
118
  # Replace the Sidekiq logger
111
119
  if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
@@ -115,14 +123,14 @@ module RailsSemanticLogger
115
123
 
116
124
  ::Sidekiq.configure_server do |config|
117
125
  config.logger = ::SemanticLogger[::Sidekiq]
118
- if config.respond_to?(:options)
126
+ if ::Sidekiq::VERSION.to_i < 6 || (::Sidekiq::VERSION.to_i == 6 && ::Sidekiq::VERSION.to_f < 6.5)
119
127
  config.options[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
120
128
  else
121
129
  config[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
122
130
  end
123
131
 
124
- # Add back the default console logger unless already added
125
- SemanticLogger.add_appender(io: $stdout, formatter: :color) unless SemanticLogger.appenders.console_output?
132
+ # Add back the default console logger (unless the app declared its own appenders, or one exists)
133
+ RailsSemanticLogger.add_server_appenders
126
134
 
127
135
  # Replace default error handler when present
128
136
  existing = RailsSemanticLogger::Sidekiq::Defaults.delete_default_error_handler(config.error_handlers)
@@ -136,20 +144,29 @@ module RailsSemanticLogger
136
144
  end
137
145
  end
138
146
 
147
+ # Replace the SolidQueue logger
148
+ if config.rails_semantic_logger.replace_solid_queue_logger && defined?(::SolidQueue) && ::SolidQueue.respond_to?(:logger=)
149
+ ::SolidQueue.logger = SemanticLogger[::SolidQueue]
150
+ end
151
+
139
152
  # Replace the Sidetiq logger
140
153
  Sidetiq.logger = SemanticLogger[Sidetiq] if defined?(Sidetiq) && Sidetiq.respond_to?(:logger=)
141
154
 
142
155
  # Replace the DelayedJob logger
143
- if defined?(Delayed::Worker)
144
- Delayed::Worker.logger = SemanticLogger[Delayed::Worker]
145
- Delayed::Worker.plugins << RailsSemanticLogger::DelayedJob::Plugin
146
- end
156
+ Delayed::Worker.logger = SemanticLogger[Delayed::Worker] if defined?(Delayed::Worker)
147
157
 
148
158
  # Replace the Bugsnag logger
149
159
  Bugsnag.configure(false) { |config| config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
150
160
 
151
161
  # Set the IOStreams PGP logger
152
- IOStreams::Pgp.logger = SemanticLogger["IOStreams::Pgp"] if defined?(IOStreams)
162
+ if defined?(IOStreams)
163
+ if IOStreams.respond_to?(:logger=)
164
+ IOStreams.logger = SemanticLogger[IOStreams]
165
+ else
166
+ # Older IOStreams versions
167
+ IOStreams::Pgp.logger = SemanticLogger[IOStreams::Pgp]
168
+ end
169
+ end
153
170
  end
154
171
 
155
172
  # After any initializers run, but after the gems have been loaded
@@ -226,7 +243,9 @@ module RailsSemanticLogger
226
243
  if defined?(::ActionController)
227
244
  require "action_controller/log_subscriber"
228
245
 
229
- RailsSemanticLogger::ActionController::LogSubscriber.action_message_format = config.rails_semantic_logger.action_message_format
246
+ subscriber = RailsSemanticLogger::ActionController::LogSubscriber
247
+ subscriber.action_message_format = config.rails_semantic_logger.action_message_format
248
+ subscriber.processing_log_level = :info if config.rails_semantic_logger.processing
230
249
  RailsSemanticLogger.swap_subscriber(
231
250
  ::ActionController::LogSubscriber,
232
251
  RailsSemanticLogger::ActionController::LogSubscriber,
@@ -248,43 +267,35 @@ module RailsSemanticLogger
248
267
  if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
249
268
  require("rails_semantic_logger/extensions/sidekiq/sidekiq")
250
269
  end
251
- end
252
270
 
253
- #
254
- # Forking Frameworks
255
- #
256
-
257
- # Passenger provides the :starting_worker_process event for executing
258
- # code after it has forked, so we use that and reconnect immediately.
259
- if defined?(PhusionPassenger)
260
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
261
- SemanticLogger.reopen if forked
271
+ # SolidQueue
272
+ if config.rails_semantic_logger.replace_solid_queue_logger && defined?(::SolidQueue::LogSubscriber)
273
+ RailsSemanticLogger.swap_subscriber(
274
+ ::SolidQueue::LogSubscriber,
275
+ RailsSemanticLogger::SolidQueue::LogSubscriber,
276
+ :solid_queue
277
+ )
262
278
  end
263
279
  end
264
280
 
265
- # Re-open appenders after Resque has forked a worker
266
- Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque.after_fork)
267
-
268
- # Re-open appenders after Spring has forked a process
269
- Spring.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Spring.after_fork)
270
-
271
- # Re-open appenders after SolidQueue worker/dispatcher/scheduler has finished booting
272
- SolidQueue.on_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_start)
273
- SolidQueue.on_worker_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_worker_start)
274
- SolidQueue.on_dispatcher_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_dispatcher_start)
275
- SolidQueue.on_scheduler_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_scheduler_start)
281
+ # Forking frameworks (Passenger, Resque, Spring, SolidQueue, etc.) no longer
282
+ # need explicit reopen hooks: Semantic Logger v5 reopens appenders automatically
283
+ # in the child via a Process._fork / Process.daemon hook. Apps that opt out with
284
+ # `SemanticLogger.reopen_on_fork = false` are responsible for reopening themselves.
276
285
 
277
286
  console do |_app|
278
287
  # Don't use a background thread for logging
279
288
  SemanticLogger.sync!
280
289
  # Add a stderr logger when running inside a Rails console unless one has already been added.
281
- if config.rails_semantic_logger.console_logger && !SemanticLogger.appenders.console_output?
282
- SemanticLogger.add_appender(io: STDERR, formatter: :color)
283
- end
290
+ RailsSemanticLogger.add_console_appenders
284
291
 
285
292
  # Include method names on log entries in the console
286
293
  SemanticLogger.backtrace_level = SemanticLogger.default_level
287
294
  end
295
+
296
+ # Initialization is complete; the post-init settings have been consumed, so
297
+ # flag changes made after this point (e.g. at runtime) as having no effect.
298
+ config.rails_semantic_logger.fully_initialized!
288
299
  end
289
300
  end
290
301
  end
@@ -1,11 +1,52 @@
1
1
  require "action_cable/connection/tagged_logger_proxy"
2
2
 
3
+ # Upstream source (identical across supported versions):
4
+ # https://github.com/rails/rails/blob/v7.2.2/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
5
+ # https://github.com/rails/rails/blob/v8.0.2/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
6
+ # https://github.com/rails/rails/blob/v8.1.3/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb
3
7
  module ActionCable
4
8
  module Connection
5
9
  class TaggedLoggerProxy
6
- def tag(logger, &block)
7
- current_tags = tags - logger.tags
8
- logger.tagged(*current_tags, &block)
10
+ # Mirrors upstream #tag, including the `respond_to?(:tagged)` guard so a
11
+ # target logger without tagging support (e.g. when handed a non-tagged
12
+ # ActiveRecord::Base.logger by the worker pool) simply yields. Diverges in
13
+ # one place: upstream reads the already-applied tags via
14
+ # `logger.formatter.current_tags` (ActiveSupport::TaggedLogging), but
15
+ # Semantic Logger exposes them via `#tags`, so resolve from whichever the
16
+ # target logger supports. See #220.
17
+ def tag(logger, &)
18
+ if logger.respond_to?(:tagged)
19
+ current_tags = tags - applied_tags_for(logger)
20
+ logger.tagged(*current_tags, &)
21
+ else
22
+ yield
23
+ end
24
+ end
25
+
26
+ # Upstream defines the severity methods with a single-arg signature
27
+ # (`message = nil`), which discards Semantic Logger's richer payload and
28
+ # exception arguments and raises ArgumentError when they are supplied.
29
+ # Redefine them to forward the full Semantic Logger signature down to the
30
+ # wrapped logger, while still applying the connection's tags. See #220.
31
+ %i[debug info warn error fatal unknown].each do |severity|
32
+ define_method(severity) do |message = nil, payload = nil, exception = nil, &block|
33
+ tag(@logger) { @logger.public_send(severity, message, payload, exception, &block) }
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # Tags already applied to the target logger, so they are not duplicated.
40
+ # Semantic Logger exposes them via `#tags`; ActiveSupport::TaggedLogging
41
+ # via `formatter.current_tags`. Fall back to none when neither is present.
42
+ def applied_tags_for(logger)
43
+ if logger.respond_to?(:tags)
44
+ Array(logger.tags)
45
+ elsif logger.respond_to?(:formatter) && logger.formatter.respond_to?(:current_tags)
46
+ logger.formatter.current_tags
47
+ else
48
+ []
49
+ end
9
50
  end
10
51
  end
11
52
  end
@@ -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