rails_semantic_logger 4.20.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +55 -98
- data/Rakefile +7 -4
- data/lib/rails_semantic_logger/action_controller/log_subscriber.rb +86 -16
- data/lib/rails_semantic_logger/action_mailer/log_subscriber.rb +36 -22
- data/lib/rails_semantic_logger/action_view/log_subscriber.rb +74 -40
- data/lib/rails_semantic_logger/active_job/log_subscriber.rb +216 -7
- data/lib/rails_semantic_logger/active_record/log_subscriber.rb +62 -160
- data/lib/rails_semantic_logger/appenders.rb +91 -0
- data/lib/rails_semantic_logger/engine.rb +47 -36
- data/lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb +44 -3
- data/lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb +5 -14
- data/lib/rails_semantic_logger/extensions/active_job/logging.rb +2 -2
- data/lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb +2 -2
- data/lib/rails_semantic_logger/extensions/active_support/logger.rb +24 -15
- data/lib/rails_semantic_logger/extensions/rails/server.rb +1 -1
- data/lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb +4 -4
- data/lib/rails_semantic_logger/options.rb +171 -20
- data/lib/rails_semantic_logger/rack/logger.rb +6 -13
- data/lib/rails_semantic_logger/sidekiq/defaults.rb +4 -2
- data/lib/rails_semantic_logger/sidekiq/job_logger.rb +13 -5
- data/lib/rails_semantic_logger/solid_queue/log_subscriber.rb +179 -0
- data/lib/rails_semantic_logger/version.rb +1 -1
- data/lib/rails_semantic_logger.rb +81 -26
- metadata +15 -21
- data/lib/rails_semantic_logger/delayed_job/plugin.rb +0 -11
- data/lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb +0 -13
- data/lib/rails_semantic_logger/extensions/rack/server.rb +0 -12
- data/lib/rails_semantic_logger/extensions/rackup/server.rb +0 -12
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
require "active_support/log_subscriber"
|
|
2
2
|
|
|
3
|
+
# This subscriber is a reimplementation of Rails' own ActionView::LogSubscriber that emits
|
|
4
|
+
# structured (message + payload) log entries instead of formatted text. When Rails changes its
|
|
5
|
+
# subscriber, those changes must be brought across here. Compare against the upstream source for
|
|
6
|
+
# each supported Rails version:
|
|
7
|
+
#
|
|
8
|
+
# Rails 8.1: https://github.com/rails/rails/blob/8-1-stable/actionview/lib/action_view/log_subscriber.rb
|
|
9
|
+
# Rails 8.0: https://github.com/rails/rails/blob/8-0-stable/actionview/lib/action_view/log_subscriber.rb
|
|
10
|
+
# Rails 7.2: https://github.com/rails/rails/blob/7-2-stable/actionview/lib/action_view/log_subscriber.rb
|
|
11
|
+
#
|
|
12
|
+
# As of these versions the upstream subscriber is identical across 7.2, 8.0, and 8.1, so no
|
|
13
|
+
# version-specific behavior is required here.
|
|
14
|
+
#
|
|
3
15
|
module RailsSemanticLogger
|
|
4
16
|
module ActionView
|
|
5
17
|
# Output Semantic logs from Action View.
|
|
6
18
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
7
|
-
VIEWS_PATTERN = %r{^app/views/}
|
|
19
|
+
VIEWS_PATTERN = %r{^app/views/}
|
|
8
20
|
|
|
9
21
|
class << self
|
|
10
22
|
attr_reader :logger
|
|
@@ -23,13 +35,15 @@ module RailsSemanticLogger
|
|
|
23
35
|
template: from_rails_root(event.payload[:identifier])
|
|
24
36
|
}
|
|
25
37
|
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
|
|
26
|
-
payload[:allocations] = event.allocations
|
|
38
|
+
payload[:allocations] = event.allocations
|
|
39
|
+
payload[:gc_time] = event.gc_time.round(2) if event.respond_to?(:gc_time)
|
|
27
40
|
|
|
28
41
|
logger.measure(
|
|
29
42
|
self.class.rendered_log_level,
|
|
30
43
|
"Rendered",
|
|
31
44
|
payload: payload,
|
|
32
|
-
duration: event.duration
|
|
45
|
+
duration: event.duration,
|
|
46
|
+
metric: "rails.view.render.template"
|
|
33
47
|
)
|
|
34
48
|
end
|
|
35
49
|
|
|
@@ -41,13 +55,33 @@ module RailsSemanticLogger
|
|
|
41
55
|
}
|
|
42
56
|
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
|
|
43
57
|
payload[:cache] = event.payload[:cache_hit] unless event.payload[:cache_hit].nil?
|
|
44
|
-
payload[:allocations] = event.allocations
|
|
58
|
+
payload[:allocations] = event.allocations
|
|
59
|
+
payload[:gc_time] = event.gc_time.round(2) if event.respond_to?(:gc_time)
|
|
45
60
|
|
|
46
61
|
logger.measure(
|
|
47
62
|
self.class.rendered_log_level,
|
|
48
63
|
"Rendered",
|
|
49
64
|
payload: payload,
|
|
50
|
-
duration: event.duration
|
|
65
|
+
duration: event.duration,
|
|
66
|
+
metric: "rails.view.render.partial"
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def render_layout(event)
|
|
71
|
+
return unless should_log?
|
|
72
|
+
|
|
73
|
+
payload = {
|
|
74
|
+
template: from_rails_root(event.payload[:identifier])
|
|
75
|
+
}
|
|
76
|
+
payload[:allocations] = event.allocations
|
|
77
|
+
payload[:gc_time] = event.gc_time.round(2) if event.respond_to?(:gc_time)
|
|
78
|
+
|
|
79
|
+
logger.measure(
|
|
80
|
+
self.class.rendered_log_level,
|
|
81
|
+
"Rendered layout",
|
|
82
|
+
payload: payload,
|
|
83
|
+
duration: event.duration,
|
|
84
|
+
metric: "rails.view.render.layout"
|
|
51
85
|
)
|
|
52
86
|
end
|
|
53
87
|
|
|
@@ -61,13 +95,15 @@ module RailsSemanticLogger
|
|
|
61
95
|
count: event.payload[:count]
|
|
62
96
|
}
|
|
63
97
|
payload[:cache_hits] = event.payload[:cache_hits] if event.payload[:cache_hits]
|
|
64
|
-
payload[:allocations] = event.allocations
|
|
98
|
+
payload[:allocations] = event.allocations
|
|
99
|
+
payload[:gc_time] = event.gc_time.round(2) if event.respond_to?(:gc_time)
|
|
65
100
|
|
|
66
101
|
logger.measure(
|
|
67
102
|
self.class.rendered_log_level,
|
|
68
103
|
"Rendered",
|
|
69
104
|
payload: payload,
|
|
70
|
-
duration: event.duration
|
|
105
|
+
duration: event.duration,
|
|
106
|
+
metric: "rails.view.render.collection"
|
|
71
107
|
)
|
|
72
108
|
end
|
|
73
109
|
|
|
@@ -83,53 +119,51 @@ module RailsSemanticLogger
|
|
|
83
119
|
super
|
|
84
120
|
end
|
|
85
121
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return unless %w[render_template.action_view render_layout.action_view].include?(name)
|
|
122
|
+
class Start
|
|
123
|
+
def start(name, _id, payload)
|
|
124
|
+
return unless %w[render_template.action_view render_layout.action_view].include?(name)
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
qualifier = " layout" if name == "render_layout.action_view"
|
|
127
|
+
payload = {template: from_rails_root(payload[:identifier])}
|
|
128
|
+
payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
|
|
94
129
|
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
logger.debug(message: "Rendering#{qualifier}", payload: payload)
|
|
131
|
+
end
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
def finish(name, id, payload)
|
|
134
|
+
end
|
|
100
135
|
|
|
101
|
-
|
|
136
|
+
private
|
|
102
137
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
def from_rails_root(string)
|
|
139
|
+
string = string.sub(rails_root, "")
|
|
140
|
+
string.sub!(VIEWS_PATTERN, "")
|
|
141
|
+
string
|
|
142
|
+
end
|
|
108
143
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
144
|
+
def rails_root
|
|
145
|
+
@root ||= "#{Rails.root}/"
|
|
146
|
+
end
|
|
112
147
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
148
|
+
def logger
|
|
149
|
+
@logger ||= ::ActionView::Base.logger
|
|
116
150
|
end
|
|
151
|
+
end
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
153
|
+
def self.attach_to(*)
|
|
154
|
+
ActiveSupport::Notifications.unsubscribe("render_template.action_view")
|
|
155
|
+
ActiveSupport::Notifications.unsubscribe("render_layout.action_view")
|
|
156
|
+
ActiveSupport::Notifications.subscribe("render_template.action_view",
|
|
157
|
+
RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
|
|
158
|
+
ActiveSupport::Notifications.subscribe("render_layout.action_view",
|
|
159
|
+
RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
|
|
125
160
|
|
|
126
|
-
|
|
127
|
-
end
|
|
161
|
+
super
|
|
128
162
|
end
|
|
129
163
|
|
|
130
164
|
private
|
|
131
165
|
|
|
132
|
-
@logger =
|
|
166
|
+
@logger = ::ActionView::Base.logger
|
|
133
167
|
@rendered_log_level = :debug
|
|
134
168
|
|
|
135
169
|
EMPTY = "".freeze
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
require "active_job"
|
|
2
2
|
|
|
3
|
+
# This subscriber is a reimplementation of Rails' own ActiveJob::LogSubscriber that emits
|
|
4
|
+
# structured (message + payload) log entries instead of formatted text. When Rails changes its
|
|
5
|
+
# subscriber, those changes must be brought across here. Compare against the upstream source for
|
|
6
|
+
# each supported Rails version:
|
|
7
|
+
#
|
|
8
|
+
# Rails 8.1: https://github.com/rails/rails/blob/8-1-stable/activejob/lib/active_job/log_subscriber.rb
|
|
9
|
+
# Rails 8.0: https://github.com/rails/rails/blob/8-0-stable/activejob/lib/active_job/log_subscriber.rb
|
|
10
|
+
# Rails 7.2: https://github.com/rails/rails/blob/7-2-stable/activejob/lib/active_job/log_subscriber.rb
|
|
11
|
+
#
|
|
12
|
+
# Event coverage by Rails version:
|
|
13
|
+
# 7.2 / 8.0: enqueue, enqueue_at, enqueue_all, perform_start, perform,
|
|
14
|
+
# enqueue_retry, retry_stopped, discard
|
|
15
|
+
# 8.1 adds (ActiveJob Continuations): interrupt, resume, step_skipped, step_started, step
|
|
16
|
+
#
|
|
17
|
+
# The Continuation handlers are defined unconditionally. On Rails < 8.1 those notifications are
|
|
18
|
+
# never emitted, so the extra methods are simply never invoked.
|
|
19
|
+
#
|
|
3
20
|
module RailsSemanticLogger
|
|
4
21
|
module ActiveJob
|
|
5
22
|
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
|
6
23
|
def enqueue(event)
|
|
7
|
-
ex = event
|
|
24
|
+
ex = enqueue_error(event)
|
|
8
25
|
|
|
9
26
|
if ex
|
|
10
27
|
log_with_formatter level: :error, event: event do |fmt|
|
|
@@ -25,7 +42,7 @@ module RailsSemanticLogger
|
|
|
25
42
|
end
|
|
26
43
|
|
|
27
44
|
def enqueue_at(event)
|
|
28
|
-
ex = event
|
|
45
|
+
ex = enqueue_error(event)
|
|
29
46
|
|
|
30
47
|
if ex
|
|
31
48
|
log_with_formatter level: :error, event: event do |fmt|
|
|
@@ -60,6 +77,11 @@ module RailsSemanticLogger
|
|
|
60
77
|
exception: ex
|
|
61
78
|
}
|
|
62
79
|
end
|
|
80
|
+
elsif event.payload[:aborted]
|
|
81
|
+
log_with_formatter event: event, log_duration: true, level: :error do |fmt|
|
|
82
|
+
{message: "Error performing #{fmt.job_info} in #{event.duration.round(2)}ms: " \
|
|
83
|
+
"a before_perform callback halted the job execution"}
|
|
84
|
+
end
|
|
63
85
|
else
|
|
64
86
|
log_with_formatter event: event, log_duration: true do |fmt|
|
|
65
87
|
{message: "Performed #{fmt.job_info} in #{event.duration.round(2)}ms"}
|
|
@@ -67,8 +89,177 @@ module RailsSemanticLogger
|
|
|
67
89
|
end
|
|
68
90
|
end
|
|
69
91
|
|
|
92
|
+
def enqueue_retry(event)
|
|
93
|
+
ex = event.payload[:error]
|
|
94
|
+
wait = event.payload[:wait]
|
|
95
|
+
|
|
96
|
+
log_with_formatter level: :info, event: event do |fmt|
|
|
97
|
+
base = "Retrying #{fmt.job_info} after #{fmt.executions} attempts in #{wait.to_i} seconds"
|
|
98
|
+
message = ex ? "#{base}, due to a #{ex.class} (#{ex.message})." : "#{base}."
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
message: message,
|
|
102
|
+
exception: ex,
|
|
103
|
+
payload: {executions: fmt.executions, wait: wait.to_i}
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def retry_stopped(event)
|
|
109
|
+
ex = event.payload[:error]
|
|
110
|
+
|
|
111
|
+
log_with_formatter level: :error, event: event do |fmt|
|
|
112
|
+
{
|
|
113
|
+
message: "Stopped retrying #{fmt.job_info} due to a #{ex.class} (#{ex.message}), " \
|
|
114
|
+
"which reoccurred on #{fmt.executions} attempts.",
|
|
115
|
+
exception: ex,
|
|
116
|
+
payload: {executions: fmt.executions}
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def discard(event)
|
|
122
|
+
ex = event.payload[:error]
|
|
123
|
+
|
|
124
|
+
log_with_formatter level: :error, event: event do |fmt|
|
|
125
|
+
{
|
|
126
|
+
message: "Discarded #{fmt.job_info} due to a #{ex.class} (#{ex.message}).",
|
|
127
|
+
exception: ex
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# ActiveJob Continuations (Rails 8.1+)
|
|
133
|
+
|
|
134
|
+
def interrupt(event)
|
|
135
|
+
description = event.payload[:description]
|
|
136
|
+
reason = event.payload[:reason]
|
|
137
|
+
|
|
138
|
+
log_with_formatter level: :info, event: event do |fmt|
|
|
139
|
+
{
|
|
140
|
+
message: "Interrupted #{fmt.job_info} #{description} (#{reason})",
|
|
141
|
+
payload: {description: description, reason: reason}
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resume(event)
|
|
147
|
+
description = event.payload[:description]
|
|
148
|
+
|
|
149
|
+
log_with_formatter level: :info, event: event do |fmt|
|
|
150
|
+
{
|
|
151
|
+
message: "Resuming #{fmt.job_info} #{description}",
|
|
152
|
+
payload: {description: description}
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def step_skipped(event)
|
|
158
|
+
step = event.payload[:step]
|
|
159
|
+
|
|
160
|
+
log_with_formatter level: :info, event: event do |fmt|
|
|
161
|
+
{
|
|
162
|
+
message: "Step '#{step.name}' skipped for #{fmt.job_info}",
|
|
163
|
+
payload: {step_name: step.name}
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def step_started(event)
|
|
169
|
+
step = event.payload[:step]
|
|
170
|
+
|
|
171
|
+
log_with_formatter level: :info, event: event do |fmt|
|
|
172
|
+
message =
|
|
173
|
+
if step.resumed?
|
|
174
|
+
"Step '#{step.name}' resumed from cursor '#{step.cursor}' for #{fmt.job_info}"
|
|
175
|
+
else
|
|
176
|
+
"Step '#{step.name}' started for #{fmt.job_info}"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
message: message,
|
|
181
|
+
payload: {step_name: step.name, step_cursor: step.cursor}
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def step(event)
|
|
187
|
+
step = event.payload[:step]
|
|
188
|
+
ex = event.payload[:exception_object]
|
|
189
|
+
|
|
190
|
+
if event.payload[:interrupted]
|
|
191
|
+
log_with_formatter level: :info, event: event, log_duration: true do |fmt|
|
|
192
|
+
{
|
|
193
|
+
message: "Step '#{step.name}' interrupted at cursor '#{step.cursor}' for " \
|
|
194
|
+
"#{fmt.job_info} in #{event.duration.round(2)}ms",
|
|
195
|
+
payload: {step_name: step.name, step_cursor: step.cursor}
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
elsif ex
|
|
199
|
+
log_with_formatter level: :error, event: event, log_duration: true do |fmt|
|
|
200
|
+
{
|
|
201
|
+
message: "Error during step '#{step.name}' at cursor '#{step.cursor}' for " \
|
|
202
|
+
"#{fmt.job_info} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message})",
|
|
203
|
+
exception: ex,
|
|
204
|
+
payload: {step_name: step.name, step_cursor: step.cursor}
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
log_with_formatter level: :info, event: event, log_duration: true do |fmt|
|
|
209
|
+
{
|
|
210
|
+
message: "Step '#{step.name}' completed for #{fmt.job_info} in #{event.duration.round(2)}ms",
|
|
211
|
+
payload: {step_name: step.name, step_cursor: step.cursor}
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def enqueue_all(event)
|
|
218
|
+
jobs = event.payload[:jobs]
|
|
219
|
+
adapter = event.payload[:adapter]
|
|
220
|
+
enqueued_count = event.payload[:enqueued_count].to_i
|
|
221
|
+
adapter_name = ::ActiveJob.adapter_name(adapter)
|
|
222
|
+
failed_count = jobs.size - enqueued_count
|
|
223
|
+
|
|
224
|
+
message =
|
|
225
|
+
if failed_count.zero?
|
|
226
|
+
enqueued_jobs_message(adapter_name, jobs)
|
|
227
|
+
elsif jobs.any?(&:successfully_enqueued?)
|
|
228
|
+
"#{enqueued_jobs_message(adapter_name, jobs.select(&:successfully_enqueued?))}. " \
|
|
229
|
+
"Failed enqueuing #{failed_count} #{'job'.pluralize(failed_count)}"
|
|
230
|
+
else
|
|
231
|
+
"Failed enqueuing #{failed_count} #{'job'.pluralize(failed_count)} to #{adapter_name}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
logger.info(
|
|
235
|
+
message: message,
|
|
236
|
+
metric: "rails.job.enqueue_all",
|
|
237
|
+
payload: {
|
|
238
|
+
event_name: event.name,
|
|
239
|
+
adapter: adapter_name,
|
|
240
|
+
enqueued_count: enqueued_count,
|
|
241
|
+
total_count: jobs.size,
|
|
242
|
+
job_classes: jobs.map { |job| job.class.name }.tally
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
70
247
|
private
|
|
71
248
|
|
|
249
|
+
# Upstream records an enqueue failure either via the event's exception_object or via
|
|
250
|
+
# ActiveJob's job.enqueue_error. Prefer the former, fall back to the latter.
|
|
251
|
+
def enqueue_error(event)
|
|
252
|
+
event.payload[:exception_object] ||
|
|
253
|
+
(event.payload[:job].respond_to?(:enqueue_error) ? event.payload[:job].enqueue_error : nil)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def enqueued_jobs_message(adapter_name, enqueued_jobs)
|
|
257
|
+
enqueued_count = enqueued_jobs.size
|
|
258
|
+
job_classes_counts = enqueued_jobs.map(&:class).tally.sort_by { |_k, v| -v }
|
|
259
|
+
"Enqueued #{enqueued_count} #{'job'.pluralize(enqueued_count)} to #{adapter_name} " \
|
|
260
|
+
"(#{job_classes_counts.map { |klass, count| "#{count} #{klass}" }.join(', ')})"
|
|
261
|
+
end
|
|
262
|
+
|
|
72
263
|
class EventFormatter
|
|
73
264
|
def initialize(event:, log_duration: false)
|
|
74
265
|
@event = event
|
|
@@ -79,6 +270,9 @@ module RailsSemanticLogger
|
|
|
79
270
|
"#{job.class.name} (Job ID: #{job.job_id}) to #{queue_name}"
|
|
80
271
|
end
|
|
81
272
|
|
|
273
|
+
# Standard payload shared by every event. enqueued_at, scheduled_at, and duration are
|
|
274
|
+
# only present when applicable (the job was scheduled, has been enqueued, or the event
|
|
275
|
+
# carries a duration), so that handlers that do not have them never emit blank keys.
|
|
82
276
|
def payload
|
|
83
277
|
{}.tap do |h|
|
|
84
278
|
h[:event_name] = event.name
|
|
@@ -86,7 +280,9 @@ module RailsSemanticLogger
|
|
|
86
280
|
h[:queue] = job.queue_name
|
|
87
281
|
h[:job_class] = job.class.name
|
|
88
282
|
h[:job_id] = job.job_id
|
|
89
|
-
h[:provider_job_id] = job.
|
|
283
|
+
h[:provider_job_id] = job.provider_job_id
|
|
284
|
+
h[:enqueued_at] = job.enqueued_at if job.respond_to?(:enqueued_at) && job.enqueued_at.present?
|
|
285
|
+
h[:scheduled_at] = scheduled_at if job.scheduled_at
|
|
90
286
|
h[:duration] = event.duration.round(2) if log_duration?
|
|
91
287
|
h[:arguments] = formatted_args
|
|
92
288
|
end
|
|
@@ -97,7 +293,11 @@ module RailsSemanticLogger
|
|
|
97
293
|
end
|
|
98
294
|
|
|
99
295
|
def scheduled_at
|
|
100
|
-
Time.at(
|
|
296
|
+
Time.at(job.scheduled_at).utc
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def executions
|
|
300
|
+
job.executions
|
|
101
301
|
end
|
|
102
302
|
|
|
103
303
|
private
|
|
@@ -144,10 +344,19 @@ module RailsSemanticLogger
|
|
|
144
344
|
end
|
|
145
345
|
end
|
|
146
346
|
|
|
347
|
+
# Builds the structured log entry for an event. The block is given an EventFormatter and
|
|
348
|
+
# returns a hash with :message, an optional :exception, and an optional :payload of extra
|
|
349
|
+
# fields. Those extra fields are merged on top of the formatter's standard payload, so
|
|
350
|
+
# handlers can add event-specific keys (executions, wait, step_name, ...) without each
|
|
351
|
+
# having to rebuild the common job payload.
|
|
147
352
|
def log_with_formatter(level: :info, **kw_args)
|
|
148
|
-
fmt
|
|
149
|
-
msg
|
|
150
|
-
|
|
353
|
+
fmt = EventFormatter.new(**kw_args)
|
|
354
|
+
msg = yield fmt
|
|
355
|
+
extra = msg.delete(:payload) || {}
|
|
356
|
+
# Emit a metric for every info/warn/error entry, named after the notification
|
|
357
|
+
# (e.g. "enqueue.active_job" -> "rails.job.enqueue"). Debug entries are excluded.
|
|
358
|
+
msg[:metric] ||= "rails.job.#{kw_args[:event].name.split('.').first}" unless level == :debug
|
|
359
|
+
logger.public_send(level, **msg, payload: fmt.payload.merge(extra))
|
|
151
360
|
end
|
|
152
361
|
|
|
153
362
|
def logger
|