rails_semantic_logger 4.19.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 +5 -2
  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 +218 -7
  8. data/lib/rails_semantic_logger/active_record/log_subscriber.rb +62 -164
  9. data/lib/rails_semantic_logger/appenders.rb +91 -0
  10. data/lib/rails_semantic_logger/engine.rb +51 -38
  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 +33 -16
  18. data/lib/rails_semantic_logger/options.rb +175 -18
  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 +41 -15
  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 -16
  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,46 +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
- RAILS_VERSION_ENDING_SET_RUNTIME_SUPPORT = Gem::Version.new( "8.0.3")
6
27
 
7
- class << self
8
- attr_reader :logger
9
- end
28
+ def strict_loading_violation(event)
29
+ return unless logger.debug?
10
30
 
11
- def self.runtime=(value)
12
- return if Rails.version >= RAILS_VERSION_ENDING_SET_RUNTIME_SUPPORT
31
+ payload = event.payload
32
+ owner = payload[:owner]
33
+ reflection = payload[:reflection]
13
34
 
14
- ::ActiveRecord::RuntimeRegistry.respond_to?(:stats) ?
15
- ::ActiveRecord::RuntimeRegistry.stats.sql_runtime = value :
16
- ::ActiveRecord::RuntimeRegistry.sql_runtime = value
17
- end
35
+ log_payload = {owner: owner.name, association: reflection.name}
36
+ log_payload[:class] = reflection.klass.name unless reflection.polymorphic?
18
37
 
19
- def self.runtime
20
- ::ActiveRecord::RuntimeRegistry.respond_to?(:stats) ?
21
- ::ActiveRecord::RuntimeRegistry.stats.sql_runtime ||= 0 :
22
- ::ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
23
- end
24
-
25
- def self.reset_runtime
26
- rt = runtime
27
- self.runtime = 0
28
- rt
38
+ logger.debug(
39
+ message: reflection.strict_loading_violation_message(owner),
40
+ payload: log_payload
41
+ )
29
42
  end
30
43
 
31
44
  def sql(event)
32
- self.class.runtime += event.duration
33
45
  return unless logger.debug?
34
46
 
35
47
  payload = event.payload
36
48
  name = payload[:name]
37
49
  return if IGNORE_PAYLOAD_NAMES.include?(name)
38
50
 
39
- log_payload = {sql: payload[:sql]}
40
- log_payload[:binds] = bind_values(payload) unless (payload[:binds] || []).empty?
41
- log_payload[:allocations] = event.allocations if event.respond_to?(:allocations)
42
- log_payload[:cached] = event.payload[:cached]
43
- 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
44
59
 
45
60
  log = {
46
61
  message: name,
@@ -58,137 +73,38 @@ module RailsSemanticLogger
58
73
 
59
74
  private
60
75
 
61
- @logger = SemanticLogger["ActiveRecord"]
62
-
63
76
  # When multiple values are received for a single bound field, it is converted into an array
64
77
  def add_bind_value(binds, key, value)
65
78
  key = key.downcase.to_sym unless key.nil?
66
79
 
67
- if rails_filter_params_include?(key)
68
- value = "[FILTERED]"
69
- elsif binds.key?(key)
70
- value = (Array(binds[key]) << value)
71
- end
72
-
80
+ value = (Array(binds[key]) << value) if binds.key?(key)
73
81
  binds[key] = value
74
82
  end
75
83
 
76
- def rails_filter_params_include?(key)
77
- filter_parameters = Rails.configuration.filter_parameters
78
-
79
- return filter_parameters.first.match? key if filter_parameters.first.is_a? Regexp
80
-
81
- filter_parameters.include? key
82
- end
83
-
84
84
  def logger
85
- self.class.logger
86
- end
87
-
88
- #
89
- # Rails 3,4,5 hell trying to get the bind values
90
- #
91
-
92
- def bind_values_v3(payload)
93
- binds = {}
94
- payload[:binds].each do |col, v|
95
- if col
96
- add_bind_value(binds, col.name, v)
97
- else
98
- binds[nil] = v
99
- end
100
- end
101
- binds
102
- end
103
-
104
- def bind_values_v4(payload)
105
- binds = {}
106
- payload[:binds].each do |col, v|
107
- attr_name, value = render_bind(col, v)
108
- add_bind_value(binds, attr_name, value)
109
- end
110
- binds
111
- end
112
-
113
- def bind_values_v5_0_0(payload)
114
- binds = {}
115
- payload[:binds].each do |attr|
116
- attr_name, value = render_bind(attr)
117
- add_bind_value(binds, attr_name, value)
118
- end
119
- binds
120
- end
121
-
122
- def bind_values_v5_0_3(payload)
123
- binds = {}
124
- casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
125
- payload[:binds].zip(casted_params).map do |attr, value|
126
- attr_name, value = render_bind(attr, value)
127
- add_bind_value(binds, attr_name, value)
128
- end
129
- binds
130
- end
131
-
132
- def bind_values_v5_1_5(payload)
133
- binds = {}
134
- casted_params = type_casted_binds(payload[:type_casted_binds])
135
- payload[:binds].zip(casted_params).map do |attr, value|
136
- attr_name, value = render_bind(attr, value)
137
- add_bind_value(binds, attr_name, value)
138
- end
139
- binds
85
+ ::ActiveRecord::Base.logger
140
86
  end
141
87
 
142
- def bind_values_v6_1(payload)
88
+ def bind_values(payload)
143
89
  binds = {}
144
90
  casted_params = type_casted_binds(payload[:type_casted_binds])
145
91
  payload[:binds].each_with_index do |attr, i|
146
- 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)
147
94
  add_bind_value(binds, attr_name, value)
148
95
  end
149
96
  binds
150
97
  end
151
98
 
152
- def render_bind_v4_2(column, value)
153
- if column
154
- if column.binary?
155
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
156
- value = value[:value] if value.is_a?(Hash)
157
- value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
158
- end
159
-
160
- [column.name, value]
161
- else
162
- [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
163
104
  end
164
105
  end
165
106
 
166
- def render_bind_v5_0_0(attribute)
167
- value =
168
- if attribute.type.binary? && attribute.value
169
- if attribute.value.is_a?(Hash)
170
- "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
171
- else
172
- "<#{attribute.value.bytesize} bytes of binary data>"
173
- end
174
- else
175
- attribute.value_for_database
176
- end
177
-
178
- [attribute.name, value]
179
- end
180
-
181
- def render_bind_v5_0_3(attr, value)
182
- if attr.is_a?(Array)
183
- attr = attr.first
184
- elsif attr.type.binary? && attr.value
185
- value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
186
- end
187
-
188
- [attr&.name, value]
189
- end
190
-
191
- def render_bind_v6_1(attr, value)
107
+ def render_bind(attr, value)
192
108
  case attr
193
109
  when ActiveModel::Attribute
194
110
  value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" if attr.type.binary? && attr.value
@@ -201,37 +117,19 @@ module RailsSemanticLogger
201
117
  [attr&.name || :nil, value]
202
118
  end
203
119
 
204
- def type_casted_binds_v5_0_3(binds, casted_binds)
205
- casted_binds || ::ActiveRecord::Base.connection.type_casted_binds(binds)
206
- 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?
207
124
 
208
- def type_casted_binds_v5_1_5(casted_binds)
209
- 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
210
129
  end
211
130
 
212
- if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 2 # 5.0.0 - 5.0.2
213
- alias bind_values bind_values_v5_0_0
214
- alias render_bind render_bind_v5_0_0
215
- elsif Rails::VERSION::MAJOR == 5 &&
216
- ((Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 6) ||
217
- (Rails::VERSION::MINOR == 1 && Rails::VERSION::TINY <= 4)) # 5.0.3 - 5.0.6 && 5.1.0 - 5.1.4
218
- alias bind_values bind_values_v5_0_3
219
- alias render_bind render_bind_v5_0_3
220
- alias type_casted_binds type_casted_binds_v5_0_3
221
- elsif (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR > 0) ||
222
- Rails::VERSION::MAJOR >= 7 # ~> 6.1.0 && >= 7.x.x
223
- alias bind_values bind_values_v6_1
224
- alias render_bind render_bind_v6_1
225
- alias type_casted_binds type_casted_binds_v5_1_5
226
- elsif Rails::VERSION::MAJOR >= 5 # ~> 5.1.5 && ~> 5.0.7 && 6.x.x
227
- alias bind_values bind_values_v5_1_5
228
- alias render_bind render_bind_v5_0_3
229
- alias type_casted_binds type_casted_binds_v5_1_5
230
- elsif Rails.version.to_i >= 4 # 4.x
231
- alias bind_values bind_values_v4
232
- alias render_bind render_bind_v4_2
233
- else # 3.x
234
- alias bind_values bind_values_v3
131
+ def type_casted_binds(casted_binds)
132
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
235
133
  end
236
134
  end
237
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,24 +113,24 @@ 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
- if defined?(::Sidekiq)
119
+ if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
112
120
  ::Sidekiq.configure_client do |config|
113
121
  config.logger = ::SemanticLogger[::Sidekiq]
114
122
  end
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,
@@ -245,44 +264,38 @@ module RailsSemanticLogger
245
264
  )
246
265
  end
247
266
 
248
- require("rails_semantic_logger/extensions/sidekiq/sidekiq") if defined?(::Sidekiq)
249
- end
250
-
251
- #
252
- # Forking Frameworks
253
- #
267
+ if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
268
+ require("rails_semantic_logger/extensions/sidekiq/sidekiq")
269
+ end
254
270
 
255
- # Passenger provides the :starting_worker_process event for executing
256
- # code after it has forked, so we use that and reconnect immediately.
257
- if defined?(PhusionPassenger)
258
- PhusionPassenger.on_event(:starting_worker_process) do |forked|
259
- 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
+ )
260
278
  end
261
279
  end
262
280
 
263
- # Re-open appenders after Resque has forked a worker
264
- Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque.after_fork)
265
-
266
- # Re-open appenders after Spring has forked a process
267
- Spring.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Spring.after_fork)
268
-
269
- # Re-open appenders after SolidQueue worker/dispatcher/scheduler has finished booting
270
- SolidQueue.on_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_start)
271
- SolidQueue.on_worker_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_worker_start)
272
- SolidQueue.on_dispatcher_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_dispatcher_start)
273
- 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.
274
285
 
275
286
  console do |_app|
276
287
  # Don't use a background thread for logging
277
288
  SemanticLogger.sync!
278
289
  # Add a stderr logger when running inside a Rails console unless one has already been added.
279
- if config.rails_semantic_logger.console_logger && !SemanticLogger.appenders.console_output?
280
- SemanticLogger.add_appender(io: STDERR, formatter: :color)
281
- end
290
+ RailsSemanticLogger.add_console_appenders
282
291
 
283
292
  # Include method names on log entries in the console
284
293
  SemanticLogger.backtrace_level = SemanticLogger.default_level
285
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!
286
299
  end
287
300
  end
288
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