rails_semantic_logger 4.14.0 → 4.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b41a1fee07d074d18bcaed7538592f123ecbbbe0f232b52cbca96133654ac256
4
- data.tar.gz: df2d6ef10cb8aa051f56a3b916064d1c51afcc3c9ef6f834c2c26be5a3dfc9b4
3
+ metadata.gz: ce106635bd9397c23bea233bc0bf69fd1e1e4fad2b4bbb28df80c1d29d25a8e4
4
+ data.tar.gz: 1690e46c1ae45b6a9846e6deff99338797a2a85f5088138465de58ee99dcefe1
5
5
  SHA512:
6
- metadata.gz: 70dec27d9976ae2edfc028bdfd03f51b3eade4cf1562681096ba3a882e2f0aacc9fba893cacd5ac7604f3810b2d6e3b880d2411d78a4372ad5b53ab579e41878
7
- data.tar.gz: 635c69136356a8c3ad62a81e0b2db7e397031878619c381fe52240405c2c0b720b9f4900fc2d2ae886d8c23943f8ebd1649444b35357c6178016bb99f036e7dd
6
+ metadata.gz: ed31c93a5312ef92bf632167e3e58c5cb421cfbe623b9af95c7119e1d88259938cee6b1870d956733002536fa154b20ff69813e5d7c86bfaa6e508feb121d551
7
+ data.tar.gz: e2aa37e8f1203cd9910ae9550fa9d60211c341b36f8b5e5b13e777f7dc9ec0361dd21adbdc4b560ffd8ba3f12e0eb1791da485ab01401f6784ff90ba25204fbb
data/README.md CHANGED
@@ -3,12 +3,116 @@
3
3
 
4
4
  Rails Semantic Logger replaces the Rails default logger with [Semantic Logger](https://logger.rocketjob.io/)
5
5
 
6
+ When any large Rails application is deployed to production one of the first steps is to move to centralized logging, so that logs can be viewed and searched from a central location.
7
+
8
+ Centralized logging quickly falls apart when trying to consume the current human readable log files:
9
+ - Log entries often span multiple lines, resulting in unrelated log lines in the centralized logging system. For example, stack traces.
10
+ - Complex Regular Expressions are needed to parse the text lines and make them machine readable. For example to build queries, or alerts that are looking for specific elements in the message.
11
+ - Writing searches, alerts, or dashboards based on text logs is incredibly brittle, since a small change to the text logged can often break the parsing of those logs.
12
+ - Every log entry often has a completely different format, making it difficult to make consistent searches against the data.
13
+
14
+ For these and many other reasons switching to structured logging, or logs in JSON format, in testing and production makes centralized logging incredibly powerful.
15
+
16
+ For example, adding these lines to `config/application.rb` and removing any other log overrides from other environments, will switch automatically to structured logging when running inside Kubernetes:
17
+ ~~~ruby
18
+ # Setup structured logging
19
+ config.semantic_logger.application = "my_application"
20
+ config.semantic_logger.environment = ENV["STACK_NAME"] || Rails.env
21
+ config.log_level = ENV["LOG_LEVEL"] || :info
22
+
23
+ # Switch to JSON Logging output to stdout when running on Kubernetes
24
+ if ENV["LOG_TO_CONSOLE"] || ENV["KUBERNETES_SERVICE_HOST"]
25
+ config.rails_semantic_logger.add_file_appender = false
26
+ config.semantic_logger.add_appender(io: $stdout, formatter: :json)
27
+ end
28
+ ~~~
29
+
30
+ Then configure the centralized logging system to tell it that the data is in JSON format, so that it will parse it for you into a hierarchy.
31
+
32
+ For example, the following will instruct [Observe](https://www.observeinc.com/) to parse the JSON data and create machine readable data from it:
33
+ ~~~ruby
34
+ interface "log", "log":log
35
+
36
+ make_col event:parse_json(log)
37
+
38
+ make_col
39
+ time:parse_isotime(event.timestamp),
40
+ application:string(event.application),
41
+ environment:string(event.environment),
42
+ duration:duration_ms(event.duration_ms),
43
+ level:string(event.level),
44
+ name:string(event.name),
45
+ message:string(event.message),
46
+ named_tags:event.named_tags,
47
+ payload:event.payload,
48
+ metric:string(event.metric),
49
+ metric_amount:float64(event.metric_amount),
50
+ tags:array(event.tags),
51
+ exception:event.exception,
52
+ host:string(event.host),
53
+ pid:int64(event.pid),
54
+ thread:string(event.thread),
55
+ file:string(event.file),
56
+ line:int64(event.line),
57
+ dimensions:event.dimensions,
58
+ backtrace:array(event.backtrace),
59
+ level_index:int64(event.level_index)
60
+
61
+ set_valid_from(time)
62
+ drop_col timestamp, log, event, stream
63
+ rename_col timestamp:time
64
+ ~~~
65
+
66
+ Now queries can be built to drill down into each of these fields, including `payload` which is a nested object.
67
+
68
+ For example to find all failed Sidekiq job calls where the causing exception class name is `NoMethodError`:
69
+ ~~~ruby
70
+ filter environment = "uat2"
71
+ filter level = "error"
72
+ filter metric = "sidekiq.job.perform"
73
+ filter (string(exception.cause.name) = "NoMethodError")
74
+ ~~~
75
+
76
+ Example: create a dashboard showing the duration of all successful Sidekiq jobs:
77
+ ~~~ruby
78
+ filter environment = "production"
79
+ filter level = "info"
80
+ filter metric = "sidekiq.job.perform"
81
+ timechart duration:avg(duration), group_by(name)
82
+ ~~~
83
+
84
+ Example: create a dashboard showing the queue latency of all Sidekiq jobs.
85
+ The queue latency is the time between when the job was enqueued and when it was started:
86
+ ~~~ruby
87
+ filter environment = "production"
88
+ filter level = "info"
89
+ filter metric = "sidekiq.queue.latency"
90
+ timechart latency:avg(metric_amount/1000), group_by(string(named_tags.queue))
91
+ ~~~
92
+
6
93
  * http://github.com/reidmorrison/rails_semantic_logger
7
94
 
8
95
  ## Documentation
9
96
 
10
97
  For complete documentation see: https://logger.rocketjob.io/rails
11
98
 
99
+ ## Upgrading to Semantic Logger V4.16 - Sidekiq Metrics Support
100
+
101
+ Rails Semantic Logger now supports Sidekiq metrics.
102
+ Below are the metrics that are now available when the JSON logging format is used:
103
+ - `sidekiq.job.perform`
104
+ - The duration of each Sidekiq job.
105
+ - `duration` contains the time in milliseconds that the job took to run.
106
+ - `sidekiq.queue.latency`
107
+ - The time between when a Sidekiq job was enqueued and when it was started.
108
+ - `metric_amount` contains the time in milliseconds that the job was waiting in the queue.
109
+
110
+ ## Upgrading to Semantic Logger v4.15 & V4.16 - Sidekiq Support
111
+
112
+ Rails Semantic Logger introduces direct support for Sidekiq v4, v5, v6, and v7.
113
+ Please remove any previous custom patches or configurations to make Sidekiq work with Semantic Logger.
114
+ To see the complete list of patches being made, and to contribute your own changes, see: [Sidekiq Patches](https://github.com/reidmorrison/rails_semantic_logger/blob/master/lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb)
115
+
12
116
  ## Upgrading to Semantic Logger v4.4
13
117
 
14
118
  With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
@@ -19,7 +123,18 @@ I.e. Please remove the following line if being called anywhere:
19
123
  SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
20
124
  ~~~
21
125
 
22
- ## Supports
126
+ ## New Versions of Rails, etc.
127
+
128
+ The primary purpose of the Rails Semantic Logger gem is to patch other gems, primarily Rails, to make them support structured logging though Semantic Logger.
129
+
130
+ When new versions of Rails and other gems are published they often make changes to the internals, so the existing patches stop working.
131
+
132
+ Rails Semantic Logger survives only when someone in the community upgrades to a newer Rails or other supported libraries, runs into problems,
133
+ and then contributes the fix back to the community by means of a pull request.
134
+
135
+ Additionally, when new popular gems come out, we rely only the community to supply the necessary patches in Rails Semantic Logger to make those gems support structured logging.
136
+
137
+ ## Supported Platforms
23
138
 
24
139
  For the complete list of supported Ruby and Rails versions, see the [Testing file](https://github.com/reidmorrison/rails_semantic_logger/blob/master/.github/workflows/ci.yml).
25
140
 
@@ -3,9 +3,13 @@ module RailsSemanticLogger
3
3
  class LogSubscriber < ActiveSupport::LogSubscriber
4
4
  INTERNAL_PARAMS = %w[controller action format _method only_path].freeze
5
5
 
6
+ class << self
7
+ attr_accessor :action_message_format
8
+ end
9
+
6
10
  # Log as debug to hide Processing messages in production
7
11
  def start_processing(event)
8
- controller_logger(event).debug { "Processing ##{event.payload[:action]}" }
12
+ controller_logger(event).debug { action_message("Processing", event.payload) }
9
13
  end
10
14
 
11
15
  def process_action(event)
@@ -59,7 +63,7 @@ module RailsSemanticLogger
59
63
  payload.delete(:response)
60
64
 
61
65
  {
62
- message: "Completed ##{payload[:action]}",
66
+ message: action_message("Completed", event.payload),
63
67
  duration: event.duration,
64
68
  payload: payload
65
69
  }
@@ -122,6 +126,14 @@ module RailsSemanticLogger
122
126
  index = path.index("?")
123
127
  index ? path[0, index] : path
124
128
  end
129
+
130
+ def action_message(message, payload)
131
+ if self.class.action_message_format
132
+ self.class.action_message_format.call(message, payload)
133
+ else
134
+ "#{message} ##{payload[:action]}"
135
+ end
136
+ end
125
137
  end
126
138
  end
127
139
  end
@@ -2,17 +2,24 @@ module RailsSemanticLogger
2
2
  module ActiveRecord
3
3
  class LogSubscriber < ActiveSupport::LogSubscriber
4
4
  IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
5
+ RAILS_VERSION_ENDING_SET_RUNTIME_SUPPORT = Gem::Version.new( "8.0.3")
5
6
 
6
7
  class << self
7
8
  attr_reader :logger
8
9
  end
9
10
 
10
11
  def self.runtime=(value)
11
- ::ActiveRecord::RuntimeRegistry.sql_runtime = value
12
+ return if Rails.version >= RAILS_VERSION_ENDING_SET_RUNTIME_SUPPORT
13
+
14
+ ::ActiveRecord::RuntimeRegistry.respond_to?(:stats) ?
15
+ ::ActiveRecord::RuntimeRegistry.stats.sql_runtime = value :
16
+ ::ActiveRecord::RuntimeRegistry.sql_runtime = value
12
17
  end
13
18
 
14
19
  def self.runtime
15
- ::ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
20
+ ::ActiveRecord::RuntimeRegistry.respond_to?(:stats) ?
21
+ ::ActiveRecord::RuntimeRegistry.stats.sql_runtime ||= 0 :
22
+ ::ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
16
23
  end
17
24
 
18
25
  def self.reset_runtime
@@ -33,6 +40,7 @@ module RailsSemanticLogger
33
40
  log_payload[:binds] = bind_values(payload) unless (payload[:binds] || []).empty?
34
41
  log_payload[:allocations] = event.allocations if event.respond_to?(:allocations)
35
42
  log_payload[:cached] = event.payload[:cached]
43
+ log_payload[:async] = true if event.payload[:async]
36
44
 
37
45
  log = {
38
46
  message: name,
@@ -54,11 +62,25 @@ module RailsSemanticLogger
54
62
 
55
63
  # When multiple values are received for a single bound field, it is converted into an array
56
64
  def add_bind_value(binds, key, value)
57
- key = key.downcase.to_sym unless key.nil?
58
- value = (Array(binds[key]) << value) if binds.key?(key)
65
+ key = key.downcase.to_sym unless key.nil?
66
+
67
+ if rails_filter_params_include?(key)
68
+ value = "[FILTERED]"
69
+ elsif binds.key?(key)
70
+ value = (Array(binds[key]) << value)
71
+ end
72
+
59
73
  binds[key] = value
60
74
  end
61
75
 
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
+
62
84
  def logger
63
85
  self.class.logger
64
86
  end
@@ -196,8 +218,8 @@ module RailsSemanticLogger
196
218
  alias bind_values bind_values_v5_0_3
197
219
  alias render_bind render_bind_v5_0_3
198
220
  alias type_casted_binds type_casted_binds_v5_0_3
199
- elsif (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR > 0) || # ~> 6.1.0
200
- Rails::VERSION::MAJOR == 7
221
+ elsif (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR > 0) ||
222
+ Rails::VERSION::MAJOR >= 7 # ~> 6.1.0 && >= 7.x.x
201
223
  alias bind_values bind_values_v6_1
202
224
  alias render_bind render_bind_v6_1
203
225
  alias type_casted_binds type_casted_binds_v5_1_5
@@ -108,12 +108,31 @@ module RailsSemanticLogger
108
108
  Resque.logger = SemanticLogger[Resque] if defined?(Resque) && Resque.respond_to?(:logger=)
109
109
 
110
110
  # Replace the Sidekiq logger
111
- if defined?(Sidekiq)
112
- if Sidekiq.respond_to?(:logger=)
113
- Sidekiq.logger = SemanticLogger[Sidekiq]
114
- elsif Sidekiq::VERSION[0..1] == "7."
115
- method = Sidekiq.server? ? :configure_server : :configure_client
116
- Sidekiq.public_send(method) { |cfg| cfg.logger = SemanticLogger[Sidekiq] }
111
+ if defined?(::Sidekiq)
112
+ ::Sidekiq.configure_client do |config|
113
+ config.logger = ::SemanticLogger[::Sidekiq]
114
+ end
115
+
116
+ ::Sidekiq.configure_server do |config|
117
+ config.logger = ::SemanticLogger[::Sidekiq]
118
+ if config.respond_to?(:options)
119
+ config.options[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
120
+ else
121
+ config[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
122
+ end
123
+
124
+ # Add back the default console logger unless already added
125
+ SemanticLogger.add_appender(io: $stdout, formatter: :color) unless SemanticLogger.appenders.console_output?
126
+
127
+ # Replace default error handler when present
128
+ existing = RailsSemanticLogger::Sidekiq::Defaults.delete_default_error_handler(config.error_handlers)
129
+ config.error_handlers << RailsSemanticLogger::Sidekiq::Defaults::ERROR_HANDLER if existing
130
+ end
131
+
132
+ if defined?(::Sidekiq::Job) && (::Sidekiq::VERSION.to_i != 5)
133
+ ::Sidekiq::Job.singleton_class.prepend(RailsSemanticLogger::Sidekiq::Loggable)
134
+ else
135
+ ::Sidekiq::Worker.singleton_class.prepend(RailsSemanticLogger::Sidekiq::Loggable)
117
136
  end
118
137
  end
119
138
 
@@ -154,7 +173,7 @@ module RailsSemanticLogger
154
173
 
155
174
  if config.rails_semantic_logger.semantic
156
175
  # Active Job
157
- if defined?(::ActiveJob) && defined?(::ActiveJob::Logging::LogSubscriber)
176
+ if defined?(::ActiveJob::Logging::LogSubscriber)
158
177
  RailsSemanticLogger.swap_subscriber(
159
178
  ::ActiveJob::Logging::LogSubscriber,
160
179
  RailsSemanticLogger::ActiveJob::LogSubscriber,
@@ -162,7 +181,7 @@ module RailsSemanticLogger
162
181
  )
163
182
  end
164
183
 
165
- if defined?(::ActiveJob) && defined?(::ActiveJob::LogSubscriber)
184
+ if defined?(::ActiveJob::LogSubscriber)
166
185
  RailsSemanticLogger.swap_subscriber(
167
186
  ::ActiveJob::LogSubscriber,
168
187
  RailsSemanticLogger::ActiveJob::LogSubscriber,
@@ -185,7 +204,7 @@ module RailsSemanticLogger
185
204
  RailsSemanticLogger::Rack::Logger.started_request_log_level = :info if config.rails_semantic_logger.started
186
205
 
187
206
  # Silence asset logging by applying a filter to the Rails logger itself, not any of the appenders.
188
- if config.rails_semantic_logger.quiet_assets && config.assets.prefix
207
+ if config.rails_semantic_logger.quiet_assets && config.respond_to?(:assets) && config.assets.prefix
189
208
  assets_root = config.relative_url_root.to_s + config.assets.prefix
190
209
  assets_regex = %r(\A/{0,2}#{assets_root})
191
210
  RailsSemanticLogger::Rack::Logger.logger.filter = ->(log) { log.payload[:path] !~ assets_regex if log.payload }
@@ -207,6 +226,7 @@ module RailsSemanticLogger
207
226
  if defined?(::ActionController)
208
227
  require "action_controller/log_subscriber"
209
228
 
229
+ RailsSemanticLogger::ActionController::LogSubscriber.action_message_format = config.rails_semantic_logger.action_message_format
210
230
  RailsSemanticLogger.swap_subscriber(
211
231
  ::ActionController::LogSubscriber,
212
232
  RailsSemanticLogger::ActionController::LogSubscriber,
@@ -224,6 +244,8 @@ module RailsSemanticLogger
224
244
  :action_mailer
225
245
  )
226
246
  end
247
+
248
+ require("rails_semantic_logger/extensions/sidekiq/sidekiq") if defined?(::Sidekiq)
227
249
  end
228
250
 
229
251
  #
@@ -239,11 +261,17 @@ module RailsSemanticLogger
239
261
  end
240
262
 
241
263
  # Re-open appenders after Resque has forked a worker
242
- Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque)
264
+ Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque.after_fork)
243
265
 
244
266
  # Re-open appenders after Spring has forked a process
245
267
  Spring.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Spring.after_fork)
246
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)
274
+
247
275
  console do |_app|
248
276
  # Don't use a background thread for logging
249
277
  SemanticLogger.sync!
@@ -6,9 +6,21 @@ module ActionDispatch
6
6
  private
7
7
 
8
8
  undef_method :log_error
9
- def log_error(_request, wrapper)
10
- ActiveSupport::Deprecation.silence do
11
- ActionController::Base.logger.fatal(wrapper.exception)
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?
13
+
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
12
24
  end
13
25
  end
14
26
  end
@@ -0,0 +1,70 @@
1
+ # Sidekiq patches
2
+ if Sidekiq::VERSION.to_i == 4
3
+ require "sidekiq/logging"
4
+ require "sidekiq/middleware/server/logging"
5
+ require "sidekiq/processor"
6
+ elsif Sidekiq::VERSION.to_i == 5
7
+ require "sidekiq/logging"
8
+ end
9
+
10
+ module Sidekiq
11
+ # Sidekiq v4 & v5
12
+ if defined?(::Sidekiq::Logging)
13
+ # Replace Sidekiq logging context
14
+ module Logging
15
+ def self.with_context(msg, &block)
16
+ SemanticLogger.tagged(msg, &block)
17
+ end
18
+
19
+ def self.job_hash_context(job_hash)
20
+ h = {jid: job_hash["jid"]}
21
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
22
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
23
+ h
24
+ end
25
+ end
26
+ end
27
+
28
+ # Sidekiq v4
29
+ if defined?(::Sidekiq::Middleware::Server::Logging)
30
+ # Convert string to machine readable format
31
+ class Processor
32
+ def log_context(job_hash)
33
+ h = {jid: job_hash["jid"]}
34
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
35
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
36
+ h
37
+ end
38
+ end
39
+
40
+ # Let Semantic Logger handle duration logging
41
+ module Middleware
42
+ module Server
43
+ class Logging
44
+ # rubocop:disable Style/ExplicitBlockArgument
45
+ def call(worker, item, queue)
46
+ SemanticLogger.tagged(queue: queue) do
47
+ worker.logger.info(
48
+ "Start #perform",
49
+ metric: "sidekiq.queue.latency",
50
+ metric_amount: job_latency_ms(item)
51
+ )
52
+ worker.logger.measure_info(
53
+ "Completed #perform",
54
+ on_exception_level: :error,
55
+ log_exception: :full,
56
+ metric: "sidekiq.job.perform"
57
+ ) { yield }
58
+ end
59
+ end
60
+
61
+ def job_latency_ms(job)
62
+ return unless job && job["enqueued_at"]
63
+
64
+ (Time.now.to_f - job["enqueued_at"].to_f) * 1000
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -22,14 +22,14 @@ module RailsSemanticLogger
22
22
  #
23
23
  # config.rails_semantic_logger.rendered = false
24
24
  #
25
- # * Override the Awesome Print options for logging Hash data as text:
25
+ # * Override the Amazing Print options for logging Hash data as text:
26
26
  #
27
- # Any valid AwesomePrint option for rendering data.
27
+ # Any valid Amazing Print option for rendering data.
28
28
  # The defaults can changed be creating a `~/.aprc` file.
29
- # See: https://github.com/michaeldv/awesome_print
29
+ # See: https://github.com/amazing-print/amazing_print
30
30
  #
31
31
  # Note: The option :multiline is set to false if not supplied.
32
- # Note: Has no effect if Awesome Print is not installed.
32
+ # Note: Has no effect if Amazing Print is not installed.
33
33
  #
34
34
  # config.rails_semantic_logger.ap_options = {multiline: false}
35
35
  #
@@ -100,23 +100,32 @@ module RailsSemanticLogger
100
100
  # * named_tags: *DEPRECATED*
101
101
  # Instead, supply a Hash to config.log_tags
102
102
  # config.rails_semantic_logger.named_tags = nil
103
+ #
104
+ # * Change the message format of Action Controller action.
105
+ # A block that will be called to format the message.
106
+ # It is supplied with the `message` and `payload` and should return the formatted data.
107
+ #
108
+ # config.rails_semantic_logger.action_message_format = -> (message, payload) do
109
+ # "#{message} - #{payload[:controller]}##{payload[:action]}"
110
+ # end
103
111
  class Options
104
112
  attr_accessor :semantic, :started, :processing, :rendered, :ap_options, :add_file_appender,
105
- :quiet_assets, :format, :named_tags, :filter, :console_logger
113
+ :quiet_assets, :format, :named_tags, :filter, :console_logger, :action_message_format
106
114
 
107
115
  # Setup default values
108
116
  def initialize
109
- @semantic = true
110
- @started = false
111
- @processing = false
112
- @rendered = false
113
- @ap_options = {multiline: false}
114
- @add_file_appender = true
115
- @quiet_assets = false
116
- @format = :default
117
- @named_tags = nil
118
- @filter = nil
119
- @console_logger = true
117
+ @semantic = true
118
+ @started = false
119
+ @processing = false
120
+ @rendered = false
121
+ @ap_options = {multiline: false}
122
+ @add_file_appender = true
123
+ @quiet_assets = false
124
+ @format = :default
125
+ @named_tags = nil
126
+ @filter = nil
127
+ @console_logger = true
128
+ @action_message_format = nil
120
129
  end
121
130
  end
122
131
  end
@@ -36,10 +36,18 @@ module RailsSemanticLogger
36
36
 
37
37
  def call_app(request, env)
38
38
  instrumenter = ActiveSupport::Notifications.instrumenter
39
- instrumenter_state = instrumenter.start "request.action_dispatch", request: request
40
- instrumenter_finish = lambda {
41
- instrumenter.finish_with_state(instrumenter_state, "request.action_dispatch", request: request)
42
- }
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
43
51
 
44
52
  logger.send(self.class.started_request_log_level) { started_request_message(request) }
45
53
  status, headers, body = @app.call(env)
@@ -0,0 +1,40 @@
1
+ module RailsSemanticLogger
2
+ module Sidekiq
3
+ module Defaults
4
+ # Prevent exception logging during standard error handling since the Job Logger below already logs the exception.
5
+ ERROR_HANDLER =
6
+ if ::Sidekiq::VERSION.to_f < 7.1 ||
7
+ (::Sidekiq::VERSION.to_f == 7.1 && ::Sidekiq::VERSION.split(".").last.to_i < 6)
8
+ lambda do |_ex, ctx|
9
+ unless ctx.empty?
10
+ job_hash = ctx[:job] || {}
11
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
12
+ logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
13
+ ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
14
+ end
15
+ end
16
+ else
17
+ lambda do |_ex, ctx, _default_configuration|
18
+ unless ctx.empty?
19
+ job_hash = ctx[:job] || {}
20
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
21
+ logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
22
+ ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
23
+ end
24
+ end
25
+ end
26
+
27
+ # Returns the default logger after removing from the supplied list.
28
+ # Returns [nil] when the default logger was not present.
29
+ def self.delete_default_error_handler(error_handlers)
30
+ return error_handlers.delete(::Sidekiq::Config::ERROR_HANDLER) if defined?(::Sidekiq::Config::ERROR_HANDLER)
31
+ return error_handlers.delete(::Sidekiq::DEFAULT_ERROR_HANDLER) if defined?(::Sidekiq::DEFAULT_ERROR_HANDLER)
32
+
33
+ return unless defined?(::Sidekiq::ExceptionHandler)
34
+
35
+ existing = error_handlers.find { |handler| handler.is_a?(::Sidekiq::ExceptionHandler::Logger) }
36
+ error_handlers.delete(existing) if existing
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,59 @@
1
+ module RailsSemanticLogger
2
+ module Sidekiq
3
+ class JobLogger
4
+ # Sidekiq 6.5 does not take any arguments, whereas v7 is given a logger
5
+ def initialize(*_args)
6
+ end
7
+
8
+ def call(item, queue, &block)
9
+ klass = item["wrapped"] || item["class"]
10
+ logger = klass ? SemanticLogger[klass] : Sidekiq.logger
11
+
12
+ SemanticLogger.tagged(queue: queue) do
13
+ # Latency is the time between when the job was enqueued and when it started executing.
14
+ logger.info(
15
+ "Start #perform",
16
+ metric: "sidekiq.queue.latency",
17
+ metric_amount: job_latency_ms(item)
18
+ )
19
+
20
+ # Measure the duration of running the job
21
+ logger.measure_info(
22
+ "Completed #perform",
23
+ on_exception_level: :error,
24
+ log_exception: :full,
25
+ metric: "sidekiq.job.perform",
26
+ &block
27
+ )
28
+ end
29
+ end
30
+
31
+ def prepare(job_hash, &block)
32
+ level = job_hash["log_level"]
33
+ if level
34
+ SemanticLogger.silence(level) do
35
+ SemanticLogger.tagged(job_hash_context(job_hash), &block)
36
+ end
37
+ else
38
+ SemanticLogger.tagged(job_hash_context(job_hash), &block)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def job_hash_context(job_hash)
45
+ h = {jid: job_hash["jid"]}
46
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
47
+ h[:tags] = job_hash["tags"] if job_hash["tags"]
48
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
49
+ h
50
+ end
51
+
52
+ def job_latency_ms(job)
53
+ return unless job && job["enqueued_at"]
54
+
55
+ (Time.now.to_f - job["enqueued_at"].to_f) * 1000
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,10 @@
1
+ module RailsSemanticLogger
2
+ module Sidekiq
3
+ module Loggable
4
+ def included(base)
5
+ super
6
+ base.include(SemanticLogger::Loggable)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsSemanticLogger
2
- VERSION = "4.14.0".freeze
2
+ VERSION = "4.19.0".freeze
3
3
  end
@@ -31,6 +31,12 @@ module RailsSemanticLogger
31
31
  autoload :Plugin, "rails_semantic_logger/delayed_job/plugin"
32
32
  end
33
33
 
34
+ module Sidekiq
35
+ autoload :Defaults, "rails_semantic_logger/sidekiq/defaults"
36
+ autoload :JobLogger, "rails_semantic_logger/sidekiq/job_logger"
37
+ autoload :Loggable, "rails_semantic_logger/sidekiq/loggable"
38
+ end
39
+
34
40
  autoload :Options, "rails_semantic_logger/options"
35
41
 
36
42
  # Swap an existing subscriber with a new one
@@ -43,7 +49,7 @@ module RailsSemanticLogger
43
49
 
44
50
  def self.unattach(subscriber)
45
51
  subscriber_patterns(subscriber).each do |pattern|
46
- ActiveSupport::Notifications.notifier.listeners_for(pattern).each do |sub|
52
+ listeners_for(ActiveSupport::Notifications.notifier, pattern).each do |sub|
47
53
  next unless sub.instance_variable_get(:@delegate) == subscriber
48
54
 
49
55
  ActiveSupport::Notifications.unsubscribe(sub)
@@ -61,12 +67,26 @@ module RailsSemanticLogger
61
67
  end
62
68
  end
63
69
 
64
- private_class_method :subscriber_patterns, :unattach
70
+ def self.listeners_for(notifier, pattern)
71
+ if notifier.respond_to?(:all_listeners_for) # Rails >= 7.1
72
+ notifier.all_listeners_for(pattern)
73
+ else
74
+ notifier.listeners_for(pattern)
75
+ end
76
+ end
77
+
78
+ private_class_method :listeners_for, :subscriber_patterns, :unattach
65
79
  end
66
80
 
67
81
  require("rails_semantic_logger/extensions/mongoid/config") if defined?(Mongoid)
68
82
  require("rails_semantic_logger/extensions/active_support/logger") if defined?(ActiveSupport::Logger)
69
83
  require("rails_semantic_logger/extensions/active_support/log_subscriber") if defined?(ActiveSupport::LogSubscriber)
84
+
85
+ begin
86
+ require "rackup"
87
+ rescue LoadError
88
+ # No need to do anything, will fall back to Rack
89
+ end
70
90
  if defined?(Rackup::Server)
71
91
  require("rails_semantic_logger/extensions/rackup/server")
72
92
  elsif defined?(Rack::Server)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_semantic_logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.14.0
4
+ version: 4.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-11-16 00:00:00.000000000 Z
10
+ date: 2025-12-09 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rack
@@ -44,16 +43,14 @@ dependencies:
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '4.13'
46
+ version: '4.16'
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '4.13'
55
- description:
56
- email:
53
+ version: '4.16'
57
54
  executables: []
58
55
  extensions: []
59
56
  extra_rdoc_files: []
@@ -82,8 +79,12 @@ files:
82
79
  - lib/rails_semantic_logger/extensions/rack/server.rb
83
80
  - lib/rails_semantic_logger/extensions/rackup/server.rb
84
81
  - lib/rails_semantic_logger/extensions/rails/server.rb
82
+ - lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb
85
83
  - lib/rails_semantic_logger/options.rb
86
84
  - lib/rails_semantic_logger/rack/logger.rb
85
+ - lib/rails_semantic_logger/sidekiq/defaults.rb
86
+ - lib/rails_semantic_logger/sidekiq/job_logger.rb
87
+ - lib/rails_semantic_logger/sidekiq/loggable.rb
87
88
  - lib/rails_semantic_logger/version.rb
88
89
  homepage: https://logger.rocketjob.io
89
90
  licenses:
@@ -91,9 +92,8 @@ licenses:
91
92
  metadata:
92
93
  bug_tracker_uri: https://github.com/reidmorrison/rails_semantic_logger/issues
93
94
  documentation_uri: https://logger.rocketjob.io
94
- source_code_uri: https://github.com/reidmorrison/rails_semantic_logger/tree/v4.14.0
95
+ source_code_uri: https://github.com/reidmorrison/rails_semantic_logger/tree/v4.19.0
95
96
  rubygems_mfa_required: 'true'
96
- post_install_message:
97
97
  rdoc_options: []
98
98
  require_paths:
99
99
  - lib
@@ -108,8 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  requirements: []
111
- rubygems_version: 3.4.9
112
- signing_key:
111
+ rubygems_version: 3.6.2
113
112
  specification_version: 4
114
113
  summary: Feature rich logging framework that replaces the Rails logger.
115
114
  test_files: []