rails_semantic_logger 4.12.0 → 4.16.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: 73a37ac27f2cf94d083cc75aa557a778f32e2de852b3be6447b072dc1da89047
4
- data.tar.gz: 681f2145e71def6b336792fe3d581dfd89da6fd3ca471befcbd8ffa4f11abb50
3
+ metadata.gz: 7da18e2122c13f2b4325a6f3000dd74d22517608b3a32355ca3d2bf4e5f08789
4
+ data.tar.gz: b0503c3227cb0bfa5faa1c6f00b408968783c8d09092a960497ab9f004798875
5
5
  SHA512:
6
- metadata.gz: 8d299a14cb4c3eaf282e4bf502d8e60c2a36095b138c17969cf0a8f79b0431a731adbe8963ae2017bf429f12b7f42fe65d5c199a2c5f88e984a71d4605766bea
7
- data.tar.gz: d837dc46c0dcd38b19dcf2a62c926c91ef1775542d55c8919d3a76db2e62764ab8675890238da78ab19b157f3b920e9d92257d5b2e347bb66d0c5a2bde60441f
6
+ metadata.gz: 30446e6a7553868e37cb56550d0a3e5c15b137991609e2cbb7aee84c7c7f5a75abe0bdd7f5e6263418546645f7a0892472e042ee09e0764be9f0ff9d90311917
7
+ data.tar.gz: 051ff94e24b1d9a4231f01d4148a966333b8d7b57539f7fa15ab953ad0a2fc9c998b61413ae2350716763f323eafa6d7302893b0c7b691e165c2200f7a8f3ef1
data/README.md CHANGED
@@ -3,12 +3,112 @@
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 duration:avg(duration), group_by(name)
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` - The duration of each Sidekiq job.
104
+ - `sidekiq.queue.latency` - The time between when a Sidekiq job was enqueued and when it was started.
105
+
106
+ ## Upgrading to Semantic Logger v4.15 & V4.16 - Sidekiq Support
107
+
108
+ Rails Semantic Logger introduces direct support for Sidekiq v4, v5, v6, and v7.
109
+ Please remove any previous custom patches or configurations to make Sidekiq work with Semantic Logger.
110
+ 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)
111
+
12
112
  ## Upgrading to Semantic Logger v4.4
13
113
 
14
114
  With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
@@ -19,7 +119,18 @@ I.e. Please remove the following line if being called anywhere:
19
119
  SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
20
120
  ~~~
21
121
 
22
- ## Supports
122
+ ## New Versions of Rails, etc.
123
+
124
+ 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.
125
+
126
+ When new versions of Rails and other gems are published they often make changes to the internals, so the existing patches stop working.
127
+
128
+ Rails Semantic Logger survives only when someone in the community upgrades to a newer Rails or other supported libraries, runs into problems,
129
+ and then contributes the fix back to the community by means of a pull request.
130
+
131
+ 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.
132
+
133
+ ## Supported Platforms
23
134
 
24
135
  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
136
 
@@ -14,10 +14,10 @@ module RailsSemanticLogger
14
14
 
15
15
  # Unused, but needed for Devise 401 status code monkey patch to still work.
16
16
  ::ActionController::Base.log_process_action(payload)
17
-
17
+
18
18
  params = payload[:params]
19
19
 
20
- if params.kind_of?(Hash) || params.kind_of?(::ActionController::Parameters)
20
+ if params.is_a?(Hash) || params.is_a?(::ActionController::Parameters)
21
21
  # According to PR https://github.com/reidmorrison/rails_semantic_logger/pull/37/files
22
22
  # params is not always a Hash.
23
23
  payload[:params] = params.to_unsafe_h unless params.is_a?(Hash)
@@ -79,7 +79,9 @@ module RailsSemanticLogger
79
79
  end
80
80
 
81
81
  def send_data(event)
82
- controller_logger(event).info(message: "Sent data", payload: {file_name: event.payload[:filename]}, duration: event.duration)
82
+ controller_logger(event).info(message: "Sent data",
83
+ payload: {file_name: event.payload[:filename]},
84
+ duration: event.duration)
83
85
  end
84
86
 
85
87
  def unpermitted_parameters(event)
@@ -9,22 +9,22 @@ module RailsSemanticLogger
9
9
  message_id = event.payload[:message_id]
10
10
  duration = event.duration.round(1)
11
11
  if ex
12
- log_with_formatter event: event, log_duration: true, level: :error do |fmt|
12
+ log_with_formatter event: event, log_duration: true, level: :error do |_fmt|
13
13
  {
14
- message: "Error delivering mail #{message_id} (#{duration}ms)",
14
+ message: "Error delivering mail #{message_id} (#{duration}ms)",
15
15
  exception: ex
16
16
  }
17
17
  end
18
18
  else
19
- message = begin
19
+ message =
20
20
  if event.payload[:perform_deliveries]
21
21
  "Delivered mail #{message_id} (#{duration}ms)"
22
22
  else
23
23
  "Skipped delivery of mail #{message_id} as `perform_deliveries` is false"
24
24
  end
25
- end
26
- log_with_formatter event: event, log_duration: true do |fmt|
27
- { message: message }
25
+
26
+ log_with_formatter event: event, log_duration: true do |_fmt|
27
+ {message: message}
28
28
  end
29
29
  end
30
30
  end
@@ -34,8 +34,8 @@ module RailsSemanticLogger
34
34
  mailer = event.payload[:mailer]
35
35
  action = event.payload[:action]
36
36
  duration = event.duration.round(1)
37
- log_with_formatter event: event do |fmt|
38
- { message: "#{mailer}##{action}: processed outbound mail in #{duration}ms" }
37
+ log_with_formatter event: event do |_fmt|
38
+ {message: "#{mailer}##{action}: processed outbound mail in #{duration}ms"}
39
39
  end
40
40
  end
41
41
 
@@ -74,8 +74,6 @@ module RailsSemanticLogger
74
74
  event.payload[:date].to_time.utc
75
75
  elsif event.payload[:date].is_a?(String)
76
76
  Time.parse(date).utc
77
- else
78
- nil
79
77
  end
80
78
  end
81
79
 
@@ -92,10 +90,10 @@ module RailsSemanticLogger
92
90
  end
93
91
 
94
92
  def formatted_args
95
- if defined?(mailer.contantize.log_arguments?) && !mailer.contantize.log_arguments?
93
+ if defined?(mailer.constantize.log_arguments?) && !mailer.constantize.log_arguments?
96
94
  ""
97
- else
98
- JSON.pretty_generate(event.payload[:args].map { |arg| format(arg) }) if event.payload[:args].present?
95
+ elsif event.payload[:args].present?
96
+ JSON.pretty_generate(event.payload[:args].map { |arg| format(arg) })
99
97
  end
100
98
  end
101
99
 
@@ -22,7 +22,7 @@ module RailsSemanticLogger
22
22
  payload = {
23
23
  template: from_rails_root(event.payload[:identifier])
24
24
  }
25
- payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
25
+ payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
26
26
  payload[:allocations] = event.allocations if event.respond_to?(:allocations)
27
27
 
28
28
  logger.measure(
@@ -39,8 +39,8 @@ module RailsSemanticLogger
39
39
  payload = {
40
40
  partial: from_rails_root(event.payload[:identifier])
41
41
  }
42
- payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
43
- payload[:cache] = event.payload[:cache_hit] unless event.payload[:cache_hit].nil?
42
+ payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
43
+ payload[:cache] = event.payload[:cache_hit] unless event.payload[:cache_hit].nil?
44
44
  payload[:allocations] = event.allocations if event.respond_to?(:allocations)
45
45
 
46
46
  logger.measure(
@@ -60,7 +60,7 @@ module RailsSemanticLogger
60
60
  template: from_rails_root(identifier),
61
61
  count: event.payload[:count]
62
62
  }
63
- payload[:cache_hits] = event.payload[:cache_hits] if event.payload[:cache_hits]
63
+ payload[:cache_hits] = event.payload[:cache_hits] if event.payload[:cache_hits]
64
64
  payload[:allocations] = event.allocations if event.respond_to?(:allocations)
65
65
 
66
66
  logger.measure(
@@ -72,16 +72,61 @@ module RailsSemanticLogger
72
72
  end
73
73
 
74
74
  def start(name, id, payload)
75
- if (name == "render_template.action_view") && should_log?
75
+ if ["render_template.action_view", "render_layout.action_view"].include?(name) && should_log?
76
+ qualifier = " layout" if name == "render_layout.action_view"
76
77
  payload = {template: from_rails_root(payload[:identifier])}
77
78
  payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
78
79
 
79
- logger.send(self.class.rendered_log_level, message: "Rendering", payload: payload)
80
+ logger.send(self.class.rendered_log_level, message: "Rendering#{qualifier}", payload: payload)
80
81
  end
81
82
 
82
83
  super
83
84
  end
84
85
 
86
+ if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
87
+ class Start
88
+ def start(name, _id, payload)
89
+ return unless %w[render_template.action_view render_layout.action_view].include?(name)
90
+
91
+ qualifier = " layout" if name == "render_layout.action_view"
92
+ payload = {template: from_rails_root(payload[:identifier])}
93
+ payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
94
+
95
+ logger.debug(message: "Rendering#{qualifier}", payload: payload)
96
+ end
97
+
98
+ def finish(name, id, payload)
99
+ end
100
+
101
+ private
102
+
103
+ def from_rails_root(string)
104
+ string = string.sub(rails_root, "")
105
+ string.sub!(VIEWS_PATTERN, "")
106
+ string
107
+ end
108
+
109
+ def rails_root
110
+ @root ||= "#{Rails.root}/"
111
+ end
112
+
113
+ def logger
114
+ @logger ||= SemanticLogger["ActionView"]
115
+ end
116
+ end
117
+
118
+ def self.attach_to(*)
119
+ ActiveSupport::Notifications.unsubscribe("render_template.action_view")
120
+ ActiveSupport::Notifications.unsubscribe("render_layout.action_view")
121
+ ActiveSupport::Notifications.subscribe("render_template.action_view",
122
+ RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
123
+ ActiveSupport::Notifications.subscribe("render_layout.action_view",
124
+ RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
125
+
126
+ super
127
+ end
128
+ end
129
+
85
130
  private
86
131
 
87
132
  @logger = SemanticLogger["ActionView"]
@@ -4,14 +4,44 @@ module RailsSemanticLogger
4
4
  module ActiveJob
5
5
  class LogSubscriber < ::ActiveSupport::LogSubscriber
6
6
  def enqueue(event)
7
- log_with_formatter event: event do |fmt|
8
- {message: "Enqueued #{fmt.job_info}"}
7
+ ex = event.payload[:exception_object]
8
+
9
+ if ex
10
+ log_with_formatter level: :error, event: event do |fmt|
11
+ {
12
+ message: "Failed enqueuing #{fmt.job_info} (#{ex.class} (#{ex.message})",
13
+ exception: ex
14
+ }
15
+ end
16
+ elsif event.payload[:aborted]
17
+ log_with_formatter level: :info, event: event do |fmt|
18
+ {message: "Failed enqueuing #{fmt.job_info}, a before_enqueue callback halted the enqueuing execution."}
19
+ end
20
+ else
21
+ log_with_formatter event: event do |fmt|
22
+ {message: "Enqueued #{fmt.job_info}"}
23
+ end
9
24
  end
10
25
  end
11
26
 
12
27
  def enqueue_at(event)
13
- log_with_formatter event: event do |fmt|
14
- {message: "Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"}
28
+ ex = event.payload[:exception_object]
29
+
30
+ if ex
31
+ log_with_formatter level: :error, event: event do |fmt|
32
+ {
33
+ message: "Failed enqueuing #{fmt.job_info} (#{ex.class} (#{ex.message})",
34
+ exception: ex
35
+ }
36
+ end
37
+ elsif event.payload[:aborted]
38
+ log_with_formatter level: :info, event: event do |fmt|
39
+ {message: "Failed enqueuing #{fmt.job_info}, a before_enqueue callback halted the enqueuing execution."}
40
+ end
41
+ else
42
+ log_with_formatter event: event do |fmt|
43
+ {message: "Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"}
44
+ end
15
45
  end
16
46
  end
17
47
 
@@ -26,7 +56,7 @@ module RailsSemanticLogger
26
56
  if ex
27
57
  log_with_formatter event: event, log_duration: true, level: :error do |fmt|
28
58
  {
29
- message: "Error performing #{fmt.job_info} in #{event.duration.round(2)}ms",
59
+ message: "Error performing #{fmt.job_info} in #{event.duration.round(2)}ms",
30
60
  exception: ex
31
61
  }
32
62
  end
@@ -169,9 +169,7 @@ module RailsSemanticLogger
169
169
  def render_bind_v6_1(attr, value)
170
170
  case attr
171
171
  when ActiveModel::Attribute
172
- if attr.type.binary? && attr.value
173
- value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
174
- end
172
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" if attr.type.binary? && attr.value
175
173
  when Array
176
174
  attr = attr.first
177
175
  else
@@ -77,7 +77,7 @@ module RailsSemanticLogger
77
77
  logger = SemanticLogger[Rails]
78
78
  logger.warn(
79
79
  "Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " \
80
- "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed.",
80
+ "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed.",
81
81
  e
82
82
  )
83
83
  logger
@@ -111,14 +111,14 @@ module RailsSemanticLogger
111
111
  if defined?(Sidekiq)
112
112
  if Sidekiq.respond_to?(:logger=)
113
113
  Sidekiq.logger = SemanticLogger[Sidekiq]
114
- elsif Sidekiq::VERSION[0..1] == '7.'
114
+ elsif Sidekiq::VERSION[0..1] == "7."
115
115
  method = Sidekiq.server? ? :configure_server : :configure_client
116
116
  Sidekiq.public_send(method) { |cfg| cfg.logger = SemanticLogger[Sidekiq] }
117
117
  end
118
118
  end
119
119
 
120
120
  # Replace the Sidetiq logger
121
- Sidetiq.logger = SemanticLogger[Sidetiq] if defined?(Sidetiq) && Sidetiq.respond_to?(:logger=)
121
+ Sidetiq.logger = SemanticLogger[Sidetiq] if defined?(Sidetiq) && Sidetiq.respond_to?(:logger=)
122
122
 
123
123
  # Replace the DelayedJob logger
124
124
  if defined?(Delayed::Worker)
@@ -127,7 +127,7 @@ module RailsSemanticLogger
127
127
  end
128
128
 
129
129
  # Replace the Bugsnag logger
130
- Bugsnag.configure { |config| config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
130
+ Bugsnag.configure(false) { |config| config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
131
131
 
132
132
  # Set the IOStreams PGP logger
133
133
  IOStreams::Pgp.logger = SemanticLogger["IOStreams::Pgp"] if defined?(IOStreams)
@@ -138,12 +138,14 @@ module RailsSemanticLogger
138
138
  config = Rails.application.config
139
139
 
140
140
  # Replace the Bugsnag logger
141
- Bugsnag.configure { |bugsnag_config| bugsnag_config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
141
+ Bugsnag.configure(false) { |bugsnag_config| bugsnag_config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
142
142
 
143
143
  # Rails Patches
144
144
  require("rails_semantic_logger/extensions/action_cable/tagged_logger_proxy") if defined?(::ActionCable)
145
145
  require("rails_semantic_logger/extensions/action_controller/live") if defined?(::ActionController::Live)
146
- require("rails_semantic_logger/extensions/action_dispatch/debug_exceptions") if defined?(::ActionDispatch::DebugExceptions)
146
+ if defined?(::ActionDispatch::DebugExceptions)
147
+ require("rails_semantic_logger/extensions/action_dispatch/debug_exceptions")
148
+ end
147
149
  if defined?(::ActionView::StreamingTemplateRenderer::Body)
148
150
  require("rails_semantic_logger/extensions/action_view/streaming_template_renderer")
149
151
  end
@@ -222,6 +224,8 @@ module RailsSemanticLogger
222
224
  :action_mailer
223
225
  )
224
226
  end
227
+
228
+ require("rails_semantic_logger/extensions/sidekiq/sidekiq") if defined?(::Sidekiq)
225
229
  end
226
230
 
227
231
  #
@@ -237,7 +241,7 @@ module RailsSemanticLogger
237
241
  end
238
242
 
239
243
  # Re-open appenders after Resque has forked a worker
240
- Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque)
244
+ Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque.after_fork)
241
245
 
242
246
  # Re-open appenders after Spring has forked a process
243
247
  Spring.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Spring.after_fork)
@@ -6,9 +6,17 @@ 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
+ ActionController::Base.logger.fatal(wrapper.exception)
13
+ end
14
+ end
15
+ else
16
+ def log_error(_request, wrapper)
17
+ ActiveSupport::Deprecation.silence do
18
+ ActionController::Base.logger.fatal(wrapper.exception)
19
+ end
12
20
  end
13
21
  end
14
22
  end
@@ -9,7 +9,11 @@ module ActiveJob
9
9
 
10
10
  undef_method :tag_logger
11
11
  def tag_logger(*tags, &block)
12
- logger.tagged(*tags, &block)
12
+ if logger.respond_to?(:tagged)
13
+ logger.tagged(*tags, &block)
14
+ else
15
+ yield
16
+ end
13
17
  end
14
18
  end
15
19
  end
@@ -0,0 +1,13 @@
1
+ if ActiveSupport::VERSION::STRING == "7.1.1"
2
+ require "active_support/log_subscriber"
3
+
4
+ module ActiveSupport
5
+ class LogSubscriber
6
+ # @override Rails 7.1
7
+ def silenced?(event)
8
+ native_log_level = @event_levels.fetch(event, ::Logger::Severity::FATAL)
9
+ logger.nil? || SemanticLogger::Levels.index(logger.level) > SemanticLogger::Levels.index(native_log_level)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -4,20 +4,23 @@ module ActiveSupport
4
4
  # More hacks to try and stop Rails from being it's own worst enemy.
5
5
  class Logger
6
6
  class << self
7
- undef :logger_outputs_to?, :broadcast
7
+ undef :logger_outputs_to?
8
+
9
+ # Prevent broadcasting since SemanticLogger already supports multiple loggers
10
+ if method_defined?(:broadcast)
11
+ undef :broadcast
12
+ def broadcast(_logger)
13
+ Module.new
14
+ end
15
+ end
8
16
  end
9
17
 
10
18
  # Prevent Console from trying to merge loggers
11
- def self.logger_outputs_to?(*args)
19
+ def self.logger_outputs_to?(*_args)
12
20
  true
13
21
  end
14
22
 
15
- # Prevent broadcasting since SemanticLogger already supports multiple loggers
16
- def self.broadcast(logger)
17
- Module.new
18
- end
19
-
20
- def self.new(*args, **kwargs)
23
+ def self.new(*_args, **_kwargs)
21
24
  SemanticLogger[self]
22
25
  end
23
26
  end
@@ -0,0 +1,12 @@
1
+ module RailsSemanticLogger
2
+ module Rackup
3
+ module Server
4
+ def daemonize_app
5
+ super
6
+ SemanticLogger.reopen
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ Rackup::Server.prepend(RailsSemanticLogger::Rackup::Server)
@@ -4,7 +4,7 @@ require "rails"
4
4
  module Rails
5
5
  class Server
6
6
  private
7
-
7
+
8
8
  undef_method :log_to_stdout if method_defined?(:log_to_stdout)
9
9
  def log_to_stdout
10
10
  wrapped_app # touch the app so the logger is set up
@@ -0,0 +1,242 @@
1
+ # Sidekiq patches
2
+ #
3
+ # To re-enable stdout logging for sidekiq server processes, add the following snippet to config/initializers/sidekiq.rb:
4
+ # Sidekiq.configure_server do |config|
5
+ # SemanticLogger.add_appender(io: $stdout, level: :debug, formatter: :color)
6
+ # end
7
+ if Sidekiq::VERSION.to_i == 4
8
+ require "sidekiq/exception_handler"
9
+ require "sidekiq/logging"
10
+ require "sidekiq/middleware/server/logging"
11
+ require "sidekiq/processor"
12
+ require "sidekiq/worker"
13
+ elsif Sidekiq::VERSION.to_i == 5
14
+ require "sidekiq/exception_handler"
15
+ require "sidekiq/job_logger"
16
+ require "sidekiq/logging"
17
+ require "sidekiq/worker"
18
+ elsif Sidekiq::VERSION.to_i == 6 && Sidekiq::VERSION.to_f < 6.5
19
+ require "sidekiq/exception_handler"
20
+ require "sidekiq/job_logger"
21
+ require "sidekiq/worker"
22
+ elsif Sidekiq::VERSION.to_i == 6
23
+ require "sidekiq/job_logger"
24
+ require "sidekiq/worker"
25
+ else
26
+ require "sidekiq/config"
27
+ require "sidekiq/job_logger"
28
+ require "sidekiq/job"
29
+ end
30
+
31
+ module Sidekiq
32
+ # Sidekiq > v4
33
+ if defined?(::Sidekiq::JobLogger)
34
+ # Let Semantic Logger handle duration logging
35
+ class JobLogger
36
+ def call(item, queue, &block)
37
+ klass = item["wrapped"] || item["class"]
38
+ logger = klass ? SemanticLogger[klass] : Sidekiq.logger
39
+
40
+ SemanticLogger.tagged(queue: queue) do
41
+ # Latency is the time between when the job was enqueued and when it started executing.
42
+ logger.info(
43
+ "Start #perform",
44
+ metric: "sidekiq.queue.latency",
45
+ metric_amount: job_latency_ms(item)
46
+ )
47
+
48
+ # Measure the duration of running the job
49
+ logger.measure_info(
50
+ "Completed #perform",
51
+ on_exception_level: :error,
52
+ log_exception: :full,
53
+ metric: "sidekiq.job.perform",
54
+ &block
55
+ )
56
+ end
57
+ end
58
+
59
+ def prepare(job_hash, &block)
60
+ level = job_hash["log_level"]
61
+ if level
62
+ SemanticLogger.silence(level) do
63
+ SemanticLogger.tagged(job_hash_context(job_hash), &block)
64
+ end
65
+ else
66
+ SemanticLogger.tagged(job_hash_context(job_hash), &block)
67
+ end
68
+ end
69
+
70
+ def job_hash_context(job_hash)
71
+ h = {jid: job_hash["jid"]}
72
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
73
+ h[:tags] = job_hash["tags"] if job_hash["tags"]
74
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
75
+ h
76
+ end
77
+
78
+ def job_latency_ms(job)
79
+ return unless job && job["enqueued_at"]
80
+
81
+ (Time.now.to_f - job["enqueued_at"].to_f) * 1000
82
+ end
83
+ end
84
+ end
85
+
86
+ # Sidekiq <= v6
87
+ if defined?(::Sidekiq::Logging)
88
+ # Replace Sidekiq logging context
89
+ module Logging
90
+ def self.with_context(msg, &block)
91
+ SemanticLogger.tagged(msg, &block)
92
+ end
93
+
94
+ def self.job_hash_context(job_hash)
95
+ h = {jid: job_hash["jid"]}
96
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
97
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
98
+ h
99
+ end
100
+ end
101
+ end
102
+
103
+ # Exception is already logged by Semantic Logger during the perform call
104
+ if defined?(::Sidekiq::ExceptionHandler)
105
+ # Sidekiq <= v6.5
106
+ module ExceptionHandler
107
+ class Logger
108
+ def call(_exception, ctx)
109
+ return if ctx.empty?
110
+
111
+ job_hash = ctx[:job] || {}
112
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
113
+ logger = klass ? SemanticLogger[klass] : Sidekiq.logger
114
+ ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
115
+ end
116
+ end
117
+ end
118
+ elsif defined?(::Sidekiq::Config)
119
+ # Sidekiq >= v7
120
+ class Config
121
+ remove_const :ERROR_HANDLER
122
+
123
+ ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) do
124
+ unless ctx.empty?
125
+ job_hash = ctx[:job] || {}
126
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
127
+ logger = klass ? SemanticLogger[klass] : Sidekiq.logger
128
+ ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
129
+ end
130
+ end
131
+ end
132
+ else
133
+ # Sidekiq >= 6.5
134
+ Sidekiq.error_handlers.delete(Sidekiq::DEFAULT_ERROR_HANDLER)
135
+ Sidekiq.error_handlers << ->(ex, ctx) do
136
+ unless ctx.empty?
137
+ job_hash = ctx[:job] || {}
138
+ klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
139
+ logger = klass ? SemanticLogger[klass] : Sidekiq.logger
140
+ ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Logging within each worker should use its own logger
146
+ case Sidekiq::VERSION.to_i
147
+ when 4
148
+ module Worker
149
+ def self.included(base)
150
+ if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
151
+ raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}"
152
+ end
153
+
154
+ base.extend(ClassMethods)
155
+ base.include(SemanticLogger::Loggable)
156
+ base.class_attribute :sidekiq_options_hash
157
+ base.class_attribute :sidekiq_retry_in_block
158
+ base.class_attribute :sidekiq_retries_exhausted_block
159
+ end
160
+ end
161
+ when 5
162
+ module Worker
163
+ def self.included(base)
164
+ if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
165
+ raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}"
166
+ end
167
+
168
+ base.extend(ClassMethods)
169
+ base.include(SemanticLogger::Loggable)
170
+ base.sidekiq_class_attribute :sidekiq_options_hash
171
+ base.sidekiq_class_attribute :sidekiq_retry_in_block
172
+ base.sidekiq_class_attribute :sidekiq_retries_exhausted_block
173
+ end
174
+ end
175
+ when 6
176
+ module Worker
177
+ def self.included(base)
178
+ if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
179
+ raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}"
180
+ end
181
+
182
+ base.include(Options)
183
+ base.extend(ClassMethods)
184
+ base.include(SemanticLogger::Loggable)
185
+ end
186
+ end
187
+ else
188
+ module Job
189
+ def self.included(base)
190
+ if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
191
+ raise ArgumentError, "Sidekiq::Job cannot be included in an ActiveJob: #{base.name}"
192
+ end
193
+
194
+ base.include(Options)
195
+ base.extend(ClassMethods)
196
+ base.include(SemanticLogger::Loggable)
197
+ end
198
+ end
199
+ end
200
+
201
+ if defined?(::Sidekiq::Middleware::Server::Logging)
202
+ # Sidekiq v4
203
+ # Convert string to machine readable format
204
+ class Processor
205
+ def log_context(job_hash)
206
+ h = {jid: job_hash["jid"]}
207
+ h[:bid] = job_hash["bid"] if job_hash["bid"]
208
+ h[:queue] = job_hash["queue"] if job_hash["queue"]
209
+ h
210
+ end
211
+ end
212
+
213
+ # Let Semantic Logger handle duration logging
214
+ module Middleware
215
+ module Server
216
+ class Logging
217
+ def call(worker, item, queue)
218
+ SemanticLogger.tagged(queue: queue) do
219
+ worker.logger.info(
220
+ "Start #perform",
221
+ metric: "sidekiq.queue.latency",
222
+ metric_amount: job_latency_ms(item)
223
+ )
224
+ worker.logger.measure_info(
225
+ "Completed #perform",
226
+ on_exception_level: :error,
227
+ log_exception: :full,
228
+ metric: "sidekiq.job.perform"
229
+ ) { yield }
230
+ end
231
+ end
232
+
233
+ def job_latency_ms(job)
234
+ return unless job && job["enqueued_at"]
235
+
236
+ (Time.now.to_f - job["enqueued_at"].to_f) * 1000
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -37,7 +37,7 @@ module RailsSemanticLogger
37
37
  def call_app(request, env)
38
38
  instrumenter = ActiveSupport::Notifications.instrumenter
39
39
  instrumenter_state = instrumenter.start "request.action_dispatch", request: request
40
- instrumenter_finish = -> () {
40
+ instrumenter_finish = lambda {
41
41
  instrumenter.finish_with_state(instrumenter_state, "request.action_dispatch", request: request)
42
42
  }
43
43
 
@@ -1,3 +1,3 @@
1
1
  module RailsSemanticLogger
2
- VERSION = "4.12.0".freeze
2
+ VERSION = "4.16.0".freeze
3
3
  end
@@ -6,21 +6,27 @@ module RailsSemanticLogger
6
6
  module ActionController
7
7
  autoload :LogSubscriber, "rails_semantic_logger/action_controller/log_subscriber"
8
8
  end
9
+
9
10
  module ActionMailer
10
11
  autoload :LogSubscriber, "rails_semantic_logger/action_mailer/log_subscriber"
11
12
  end
13
+
12
14
  module ActionView
13
15
  autoload :LogSubscriber, "rails_semantic_logger/action_view/log_subscriber"
14
16
  end
17
+
15
18
  module ActiveJob
16
19
  autoload :LogSubscriber, "rails_semantic_logger/active_job/log_subscriber"
17
20
  end
21
+
18
22
  module ActiveRecord
19
23
  autoload :LogSubscriber, "rails_semantic_logger/active_record/log_subscriber"
20
24
  end
25
+
21
26
  module Rack
22
27
  autoload :Logger, "rails_semantic_logger/rack/logger"
23
28
  end
29
+
24
30
  module DelayedJob
25
31
  autoload :Plugin, "rails_semantic_logger/delayed_job/plugin"
26
32
  end
@@ -48,9 +54,11 @@ module RailsSemanticLogger
48
54
  end
49
55
 
50
56
  def self.subscriber_patterns(subscriber)
51
- subscriber.patterns.respond_to?(:keys) ?
52
- subscriber.patterns.keys :
57
+ if subscriber.patterns.respond_to?(:keys)
58
+ subscriber.patterns.keys
59
+ else
53
60
  subscriber.patterns
61
+ end
54
62
  end
55
63
 
56
64
  private_class_method :subscriber_patterns, :unattach
@@ -58,4 +66,15 @@ end
58
66
 
59
67
  require("rails_semantic_logger/extensions/mongoid/config") if defined?(Mongoid)
60
68
  require("rails_semantic_logger/extensions/active_support/logger") if defined?(ActiveSupport::Logger)
61
- require("rails_semantic_logger/extensions/rack/server") if defined?(Rack::Server)
69
+ require("rails_semantic_logger/extensions/active_support/log_subscriber") if defined?(ActiveSupport::LogSubscriber)
70
+
71
+ begin
72
+ require "rackup"
73
+ rescue LoadError
74
+ # No need to do anything, will fall back to Rack
75
+ end
76
+ if defined?(Rackup::Server)
77
+ require("rails_semantic_logger/extensions/rackup/server")
78
+ elsif defined?(Rack::Server)
79
+ require("rails_semantic_logger/extensions/rack/server")
80
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_semantic_logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.0
4
+ version: 4.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-27 00:00:00.000000000 Z
11
+ date: 2024-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -75,11 +75,14 @@ files:
75
75
  - lib/rails_semantic_logger/extensions/action_view/streaming_template_renderer.rb
76
76
  - lib/rails_semantic_logger/extensions/active_job/logging.rb
77
77
  - lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb
78
+ - lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb
78
79
  - lib/rails_semantic_logger/extensions/active_support/logger.rb
79
80
  - lib/rails_semantic_logger/extensions/active_support/tagged_logging.rb
80
81
  - lib/rails_semantic_logger/extensions/mongoid/config.rb
81
82
  - lib/rails_semantic_logger/extensions/rack/server.rb
83
+ - lib/rails_semantic_logger/extensions/rackup/server.rb
82
84
  - lib/rails_semantic_logger/extensions/rails/server.rb
85
+ - lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb
83
86
  - lib/rails_semantic_logger/options.rb
84
87
  - lib/rails_semantic_logger/rack/logger.rb
85
88
  - lib/rails_semantic_logger/version.rb
@@ -89,7 +92,7 @@ licenses:
89
92
  metadata:
90
93
  bug_tracker_uri: https://github.com/reidmorrison/rails_semantic_logger/issues
91
94
  documentation_uri: https://logger.rocketjob.io
92
- source_code_uri: https://github.com/reidmorrison/rails_semantic_logger/tree/4.12.0
95
+ source_code_uri: https://github.com/reidmorrison/rails_semantic_logger/tree/v4.16.0
93
96
  rubygems_mfa_required: 'true'
94
97
  post_install_message:
95
98
  rdoc_options: []
@@ -106,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
109
  - !ruby/object:Gem::Version
107
110
  version: '0'
108
111
  requirements: []
109
- rubygems_version: 3.4.9
112
+ rubygems_version: 3.5.3
110
113
  signing_key:
111
114
  specification_version: 4
112
115
  summary: Feature rich logging framework that replaces the Rails logger.