logstruct 0.1.0 → 0.1.1
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/CHANGELOG.md +5 -1
- data/README.md +15 -2
- data/lib/log_struct/boot_buffer.rb +28 -0
- data/lib/log_struct/builders/active_job.rb +84 -0
- data/lib/log_struct/concerns/configuration.rb +126 -13
- data/lib/log_struct/concerns/error_handling.rb +3 -7
- data/lib/log_struct/config_struct/filters.rb +18 -0
- data/lib/log_struct/config_struct/integrations.rb +8 -12
- data/lib/log_struct/configuration.rb +13 -0
- data/lib/log_struct/enums/event.rb +13 -0
- data/lib/log_struct/enums/log_field.rb +154 -0
- data/lib/log_struct/enums/source.rb +4 -1
- data/lib/log_struct/formatter.rb +29 -17
- data/lib/log_struct/integrations/action_mailer/error_handling.rb +3 -11
- data/lib/log_struct/integrations/action_mailer/event_logging.rb +22 -12
- data/lib/log_struct/integrations/active_job/log_subscriber.rb +52 -48
- data/lib/log_struct/integrations/active_model_serializers.rb +8 -14
- data/lib/log_struct/integrations/active_record.rb +35 -5
- data/lib/log_struct/integrations/active_storage.rb +59 -20
- data/lib/log_struct/integrations/ahoy.rb +2 -1
- data/lib/log_struct/integrations/carrierwave.rb +13 -16
- data/lib/log_struct/integrations/dotenv.rb +278 -0
- data/lib/log_struct/integrations/good_job/log_subscriber.rb +86 -136
- data/lib/log_struct/integrations/good_job/logger.rb +8 -10
- data/lib/log_struct/integrations/good_job.rb +5 -7
- data/lib/log_struct/integrations/host_authorization.rb +25 -4
- data/lib/log_struct/integrations/lograge.rb +20 -14
- data/lib/log_struct/integrations/puma.rb +482 -0
- data/lib/log_struct/integrations/rack_error_handler/middleware.rb +11 -18
- data/lib/log_struct/integrations/shrine.rb +44 -19
- data/lib/log_struct/integrations/sorbet.rb +48 -0
- data/lib/log_struct/integrations.rb +21 -0
- data/lib/log_struct/log/action_mailer/delivered.rb +99 -0
- data/lib/log_struct/log/action_mailer/delivery.rb +99 -0
- data/lib/log_struct/log/action_mailer.rb +30 -45
- data/lib/log_struct/log/active_job/enqueue.rb +125 -0
- data/lib/log_struct/log/active_job/finish.rb +130 -0
- data/lib/log_struct/log/active_job/schedule.rb +125 -0
- data/lib/log_struct/log/active_job/start.rb +130 -0
- data/lib/log_struct/log/active_job.rb +41 -54
- data/lib/log_struct/log/active_model_serializers.rb +72 -33
- data/lib/log_struct/log/active_storage/delete.rb +87 -0
- data/lib/log_struct/log/active_storage/download.rb +103 -0
- data/lib/log_struct/log/active_storage/exist.rb +93 -0
- data/lib/log_struct/log/active_storage/metadata.rb +93 -0
- data/lib/log_struct/log/active_storage/stream.rb +93 -0
- data/lib/log_struct/log/active_storage/upload.rb +118 -0
- data/lib/log_struct/log/active_storage/url.rb +93 -0
- data/lib/log_struct/log/active_storage.rb +32 -68
- data/lib/log_struct/log/ahoy.rb +67 -33
- data/lib/log_struct/log/carrierwave/delete.rb +115 -0
- data/lib/log_struct/log/carrierwave/download.rb +131 -0
- data/lib/log_struct/log/carrierwave/upload.rb +141 -0
- data/lib/log_struct/log/carrierwave.rb +37 -72
- data/lib/log_struct/log/dotenv/load.rb +76 -0
- data/lib/log_struct/log/dotenv/restore.rb +76 -0
- data/lib/log_struct/log/dotenv/save.rb +76 -0
- data/lib/log_struct/log/dotenv/update.rb +76 -0
- data/lib/log_struct/log/dotenv.rb +12 -0
- data/lib/log_struct/log/error.rb +58 -47
- data/lib/log_struct/log/good_job/enqueue.rb +126 -0
- data/lib/log_struct/log/good_job/error.rb +151 -0
- data/lib/log_struct/log/good_job/finish.rb +136 -0
- data/lib/log_struct/log/good_job/log.rb +131 -0
- data/lib/log_struct/log/good_job/schedule.rb +136 -0
- data/lib/log_struct/log/good_job/start.rb +136 -0
- data/lib/log_struct/log/good_job.rb +40 -141
- data/lib/log_struct/log/interfaces/additional_data_field.rb +1 -17
- data/lib/log_struct/log/interfaces/common_fields.rb +1 -39
- data/lib/log_struct/log/interfaces/public_common_fields.rb +1 -28
- data/lib/log_struct/log/interfaces/request_fields.rb +1 -33
- data/lib/log_struct/log/plain.rb +59 -34
- data/lib/log_struct/log/puma/shutdown.rb +80 -0
- data/lib/log_struct/log/puma/start.rb +120 -0
- data/lib/log_struct/log/puma.rb +10 -0
- data/lib/log_struct/log/request.rb +132 -48
- data/lib/log_struct/log/security/blocked_host.rb +141 -0
- data/lib/log_struct/log/security/csrf_violation.rb +131 -0
- data/lib/log_struct/log/security/ip_spoof.rb +141 -0
- data/lib/log_struct/log/security.rb +40 -70
- data/lib/log_struct/log/shared/add_request_fields.rb +1 -26
- data/lib/log_struct/log/shared/merge_additional_data_fields.rb +1 -22
- data/lib/log_struct/log/shared/serialize_common.rb +1 -33
- data/lib/log_struct/log/shared/serialize_common_public.rb +9 -9
- data/lib/log_struct/log/shrine/delete.rb +85 -0
- data/lib/log_struct/log/shrine/download.rb +90 -0
- data/lib/log_struct/log/shrine/exist.rb +90 -0
- data/lib/log_struct/log/shrine/metadata.rb +90 -0
- data/lib/log_struct/log/shrine/upload.rb +105 -0
- data/lib/log_struct/log/shrine.rb +10 -67
- data/lib/log_struct/log/sidekiq.rb +65 -26
- data/lib/log_struct/log/sql.rb +113 -106
- data/lib/log_struct/log.rb +29 -36
- data/lib/log_struct/multi_error_reporter.rb +80 -22
- data/lib/log_struct/param_filters.rb +50 -7
- data/lib/log_struct/rails_boot_banner_silencer.rb +123 -0
- data/lib/log_struct/railtie.rb +71 -0
- data/lib/log_struct/semantic_logger/formatter.rb +4 -2
- data/lib/log_struct/semantic_logger/setup.rb +34 -18
- data/lib/log_struct/shared/interfaces/additional_data_field.rb +22 -0
- data/lib/log_struct/shared/interfaces/common_fields.rb +39 -0
- data/lib/log_struct/shared/interfaces/public_common_fields.rb +29 -0
- data/lib/log_struct/shared/interfaces/request_fields.rb +39 -0
- data/lib/log_struct/shared/shared/add_request_fields.rb +28 -0
- data/lib/log_struct/shared/shared/merge_additional_data_fields.rb +27 -0
- data/lib/log_struct/shared/shared/serialize_common.rb +58 -0
- data/lib/log_struct/version.rb +1 -1
- data/lib/log_struct.rb +22 -4
- data/logstruct.gemspec +2 -1
- metadata +78 -9
- data/lib/log_struct/log/interfaces/message_field.rb +0 -20
- data/lib/log_struct/log_keys.rb +0 -102
@@ -0,0 +1,278 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# rubocop:disable Sorbet/ConstantsFromStrings
|
5
|
+
require_relative "../boot_buffer"
|
6
|
+
require "pathname"
|
7
|
+
|
8
|
+
begin
|
9
|
+
require "dotenv-rails"
|
10
|
+
rescue LoadError
|
11
|
+
# Dotenv-rails gem is not available, integration will be skipped
|
12
|
+
end
|
13
|
+
|
14
|
+
module LogStruct
|
15
|
+
module Integrations
|
16
|
+
# Dotenv integration: emits structured logs for load/update/save/restore events
|
17
|
+
module Dotenv
|
18
|
+
extend T::Sig
|
19
|
+
extend IntegrationInterface
|
20
|
+
@original_logger_setter = T.let(nil, T.nilable(UnboundMethod))
|
21
|
+
|
22
|
+
# Internal state holder to avoid duplicate subscriptions in a Sorbet-friendly way
|
23
|
+
State = ::Struct.new(:subscribed)
|
24
|
+
STATE = T.let(State.new(false), State)
|
25
|
+
|
26
|
+
sig { override.params(config: LogStruct::Configuration).returns(T.nilable(T::Boolean)) }
|
27
|
+
def self.setup(config)
|
28
|
+
# Subscribe regardless of dotenv gem presence so instrumentation via
|
29
|
+
# ActiveSupport::Notifications can be captured during tests and runtime.
|
30
|
+
subscribe!
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
extend T::Sig
|
36
|
+
|
37
|
+
sig { void }
|
38
|
+
def subscribe!
|
39
|
+
# Guard against double subscription
|
40
|
+
return if STATE.subscribed
|
41
|
+
|
42
|
+
instrumenter = defined?(::ActiveSupport::Notifications) ? ::ActiveSupport::Notifications : nil
|
43
|
+
return unless instrumenter
|
44
|
+
|
45
|
+
instrumenter.subscribe("load.dotenv") do |*args|
|
46
|
+
# Allow tests to stub Log::Dotenv.new to force an error path
|
47
|
+
LogStruct::Log::Dotenv.new
|
48
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
49
|
+
env = event.payload[:env]
|
50
|
+
abs = env.filename
|
51
|
+
file = begin
|
52
|
+
if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root
|
53
|
+
Pathname.new(abs).relative_path_from(Pathname.new(::Rails.root.to_s)).to_s
|
54
|
+
else
|
55
|
+
abs
|
56
|
+
end
|
57
|
+
rescue
|
58
|
+
abs
|
59
|
+
end
|
60
|
+
|
61
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
62
|
+
LogStruct.info(Log::Dotenv::Load.new(file: file, timestamp: ts))
|
63
|
+
rescue => e
|
64
|
+
if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test"
|
65
|
+
raise
|
66
|
+
else
|
67
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
instrumenter.subscribe("update.dotenv") do |*args|
|
72
|
+
LogStruct::Log::Dotenv.new
|
73
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
74
|
+
diff = event.payload[:diff]
|
75
|
+
vars = diff.env.keys.map(&:to_s)
|
76
|
+
|
77
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
78
|
+
LogStruct.debug(Log::Dotenv::Update.new(vars: vars, timestamp: ts))
|
79
|
+
rescue => e
|
80
|
+
if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test"
|
81
|
+
raise
|
82
|
+
else
|
83
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
instrumenter.subscribe("save.dotenv") do |*args|
|
88
|
+
LogStruct::Log::Dotenv.new
|
89
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
90
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
91
|
+
LogStruct.info(Log::Dotenv::Save.new(snapshot: true, timestamp: ts))
|
92
|
+
rescue => e
|
93
|
+
if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test"
|
94
|
+
raise
|
95
|
+
else
|
96
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
instrumenter.subscribe("restore.dotenv") do |*args|
|
101
|
+
LogStruct::Log::Dotenv.new
|
102
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
103
|
+
diff = event.payload[:diff]
|
104
|
+
vars = diff.env.keys.map(&:to_s)
|
105
|
+
|
106
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
107
|
+
LogStruct.info(Log::Dotenv::Restore.new(vars: vars, timestamp: ts))
|
108
|
+
rescue => e
|
109
|
+
if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test"
|
110
|
+
raise
|
111
|
+
else
|
112
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
STATE.subscribed = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Early boot subscription to buffer structured logs until logger is ready
|
121
|
+
@@boot_subscribed = T.let(false, T::Boolean)
|
122
|
+
sig { void }
|
123
|
+
def self.setup_boot
|
124
|
+
return if @@boot_subscribed
|
125
|
+
return unless defined?(::ActiveSupport::Notifications)
|
126
|
+
|
127
|
+
instrumenter = if Object.const_defined?(:Dotenv)
|
128
|
+
dm = T.unsafe(Object.const_get(:Dotenv))
|
129
|
+
dm.respond_to?(:instrumenter) ? T.unsafe(dm).instrumenter : ::ActiveSupport::Notifications
|
130
|
+
else
|
131
|
+
::ActiveSupport::Notifications
|
132
|
+
end
|
133
|
+
|
134
|
+
instrumenter.subscribe("load.dotenv") do |*args|
|
135
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
136
|
+
env = event.payload[:env]
|
137
|
+
abs = env.filename
|
138
|
+
file = begin
|
139
|
+
if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root
|
140
|
+
Pathname.new(abs).relative_path_from(Pathname.new(::Rails.root.to_s)).to_s
|
141
|
+
else
|
142
|
+
abs
|
143
|
+
end
|
144
|
+
rescue
|
145
|
+
abs
|
146
|
+
end
|
147
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
148
|
+
LogStruct::BootBuffer.add(Log::Dotenv::Load.new(file: file, timestamp: ts))
|
149
|
+
rescue => e
|
150
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
151
|
+
end
|
152
|
+
|
153
|
+
instrumenter.subscribe("update.dotenv") do |*args|
|
154
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
155
|
+
diff = event.payload[:diff]
|
156
|
+
vars = diff.env.keys.map(&:to_s)
|
157
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
158
|
+
LogStruct::BootBuffer.add(Log::Dotenv::Update.new(vars: vars, timestamp: ts))
|
159
|
+
rescue => e
|
160
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
161
|
+
end
|
162
|
+
|
163
|
+
instrumenter.subscribe("save.dotenv") do |*args|
|
164
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
165
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
166
|
+
LogStruct::BootBuffer.add(Log::Dotenv::Save.new(snapshot: true, timestamp: ts))
|
167
|
+
rescue => e
|
168
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
169
|
+
end
|
170
|
+
|
171
|
+
instrumenter.subscribe("restore.dotenv") do |*args|
|
172
|
+
event = ::ActiveSupport::Notifications::Event.new(*args)
|
173
|
+
diff = event.payload[:diff]
|
174
|
+
vars = diff.env.keys.map(&:to_s)
|
175
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
176
|
+
LogStruct::BootBuffer.add(Log::Dotenv::Restore.new(vars: vars, timestamp: ts))
|
177
|
+
rescue => e
|
178
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv)
|
179
|
+
end
|
180
|
+
|
181
|
+
@@boot_subscribed = true
|
182
|
+
end
|
183
|
+
|
184
|
+
# Intercept Dotenv::Rails#logger= to defer replay until we resolve policy
|
185
|
+
sig { void }
|
186
|
+
def self.intercept_logger_setter!
|
187
|
+
return unless Object.const_defined?(:Dotenv)
|
188
|
+
# Do not intercept when LogStruct is disabled; allow original dotenv replay
|
189
|
+
return unless LogStruct.enabled?
|
190
|
+
dotenv_mod = T.unsafe(Object.const_get(:Dotenv))
|
191
|
+
return unless dotenv_mod.const_defined?(:Rails)
|
192
|
+
klass = T.unsafe(dotenv_mod.const_get(:Rails))
|
193
|
+
return if klass.instance_variable_defined?(:@_logstruct_replay_patched)
|
194
|
+
|
195
|
+
original = klass.instance_method(:logger=)
|
196
|
+
@original_logger_setter = original
|
197
|
+
|
198
|
+
mod = Module.new do
|
199
|
+
define_method :logger= do |new_logger|
|
200
|
+
# Defer replay: store desired logger, keep ReplayLogger as current
|
201
|
+
instance_variable_set(:@logstruct_pending_dotenv_logger, new_logger)
|
202
|
+
new_logger
|
203
|
+
end
|
204
|
+
|
205
|
+
define_method :logstruct_pending_dotenv_logger do
|
206
|
+
instance_variable_get(:@logstruct_pending_dotenv_logger)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
klass.prepend(mod)
|
211
|
+
klass.instance_variable_set(:@_logstruct_replay_patched, true)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Decide which boot logs to emit after user initializers
|
215
|
+
sig { void }
|
216
|
+
def self.resolve_boot_logs!
|
217
|
+
# If LogStruct is disabled, do not alter dotenv behavior at all
|
218
|
+
return unless LogStruct.enabled?
|
219
|
+
dotenv_mod = Object.const_defined?(:Dotenv) ? T.unsafe(Object.const_get(:Dotenv)) : nil
|
220
|
+
klass = dotenv_mod&.const_defined?(:Rails) ? T.unsafe(dotenv_mod.const_get(:Rails)) : nil
|
221
|
+
|
222
|
+
pending_logger = nil
|
223
|
+
railtie_instance = nil
|
224
|
+
if klass&.respond_to?(:instance)
|
225
|
+
railtie_instance = klass.instance
|
226
|
+
if railtie_instance.respond_to?(:logstruct_pending_dotenv_logger)
|
227
|
+
pending_logger = T.unsafe(railtie_instance).logstruct_pending_dotenv_logger
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
if LogStruct.enabled? && LogStruct.config.integrations.enable_dotenv
|
232
|
+
# Structured path
|
233
|
+
if pending_logger && railtie_instance
|
234
|
+
# Clear any buffered original logs
|
235
|
+
current_logger = railtie_instance.logger if railtie_instance.respond_to?(:logger)
|
236
|
+
if current_logger && current_logger.class.name.end_with?("ReplayLogger")
|
237
|
+
begin
|
238
|
+
logs = current_logger.instance_variable_get(:@logs)
|
239
|
+
logs.clear if logs.respond_to?(:clear)
|
240
|
+
rescue
|
241
|
+
# best effort
|
242
|
+
end
|
243
|
+
end
|
244
|
+
railtie_instance.config.dotenv.logger = pending_logger
|
245
|
+
end
|
246
|
+
|
247
|
+
# Detach original subscriber and subscribe runtime structured
|
248
|
+
if dotenv_mod&.const_defined?(:LogSubscriber)
|
249
|
+
T.unsafe(dotenv_mod.const_get(:LogSubscriber)).detach_from(:dotenv)
|
250
|
+
end
|
251
|
+
LogStruct::Integrations::Dotenv.subscribe!
|
252
|
+
|
253
|
+
require_relative "../boot_buffer"
|
254
|
+
LogStruct::BootBuffer.flush
|
255
|
+
else
|
256
|
+
# Original path: replay dotenv lines, drop structured buffer
|
257
|
+
if railtie_instance && @original_logger_setter
|
258
|
+
setter = @original_logger_setter
|
259
|
+
new_logger = pending_logger
|
260
|
+
if new_logger.nil? && ENV["RAILS_LOG_TO_STDOUT"].to_s.strip != ""
|
261
|
+
require "logger"
|
262
|
+
require "active_support/tagged_logging"
|
263
|
+
new_logger = ActiveSupport::TaggedLogging.new(::Logger.new($stdout)).tagged("dotenv")
|
264
|
+
end
|
265
|
+
setter.bind_call(railtie_instance, new_logger) if new_logger
|
266
|
+
end
|
267
|
+
require_relative "../boot_buffer"
|
268
|
+
LogStruct::BootBuffer.clear
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Subscribe immediately to capture earliest dotenv events into BootBuffer
|
276
|
+
LogStruct::Integrations::Dotenv.setup_boot
|
277
|
+
|
278
|
+
# rubocop:enable Sorbet/ConstantsFromStrings
|
@@ -38,168 +38,118 @@ module LogStruct
|
|
38
38
|
extend T::Sig
|
39
39
|
|
40
40
|
# Job enqueued event
|
41
|
-
sig { params(event:
|
41
|
+
sig { params(event: ::ActiveSupport::Notifications::Event).void }
|
42
42
|
def enqueue(event)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
additional_data: {
|
56
|
-
enqueue_caller: job_data[:caller_location]
|
57
|
-
}
|
58
|
-
)
|
59
|
-
|
60
|
-
logger.info(log_entry)
|
43
|
+
payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
|
44
|
+
job = payload[:job]
|
45
|
+
base_fields = build_base_fields(job, payload)
|
46
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
47
|
+
|
48
|
+
logger.info(Log::GoodJob::Enqueue.new(
|
49
|
+
**base_fields.to_kwargs,
|
50
|
+
scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
|
51
|
+
duration_ms: event.duration.to_f,
|
52
|
+
additional_data: {enqueue_caller: job&.enqueue_caller_location},
|
53
|
+
timestamp: ts
|
54
|
+
))
|
61
55
|
end
|
62
56
|
|
63
57
|
# Job execution started event
|
64
|
-
sig { params(event:
|
58
|
+
sig { params(event: ::ActiveSupport::Notifications::Event).void }
|
65
59
|
def start(event)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
60
|
+
payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
|
61
|
+
job = payload[:job]
|
62
|
+
execution = payload[:execution] || payload[:good_job_execution]
|
63
|
+
base_fields = build_base_fields(job, payload)
|
64
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
65
|
+
|
66
|
+
logger.info(Log::GoodJob::Start.new(
|
67
|
+
**base_fields.to_kwargs,
|
68
|
+
wait_ms: begin
|
69
|
+
wt = execution&.wait_time || calculate_wait_time(execution)
|
70
|
+
wt ? (wt.to_f * 1000.0) : nil
|
71
|
+
end,
|
72
|
+
scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
|
78
73
|
process_id: ::Process.pid,
|
79
|
-
thread_id: Thread.current.object_id.to_s(36)
|
80
|
-
|
81
|
-
|
82
|
-
logger.info(log_entry)
|
74
|
+
thread_id: Thread.current.object_id.to_s(36),
|
75
|
+
timestamp: ts
|
76
|
+
))
|
83
77
|
end
|
84
78
|
|
85
79
|
# Job completed successfully event
|
86
|
-
sig { params(event:
|
80
|
+
sig { params(event: ::ActiveSupport::Notifications::Event).void }
|
87
81
|
def finish(event)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
finished_at: Time.now,
|
82
|
+
payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
|
83
|
+
job = payload[:job]
|
84
|
+
base_fields = build_base_fields(job, payload)
|
85
|
+
start_ts = event.time ? Time.at(event.time) : Time.now
|
86
|
+
end_ts = event.end ? Time.at(event.end) : Time.now
|
87
|
+
|
88
|
+
logger.info(Log::GoodJob::Finish.new(
|
89
|
+
**base_fields.to_kwargs,
|
90
|
+
duration_ms: event.duration.to_f,
|
91
|
+
finished_at: end_ts,
|
99
92
|
process_id: ::Process.pid,
|
100
93
|
thread_id: Thread.current.object_id.to_s(36),
|
101
|
-
additional_data: {
|
102
|
-
|
103
|
-
|
104
|
-
)
|
105
|
-
|
106
|
-
logger.info(log_entry)
|
94
|
+
additional_data: {result: payload[:result]},
|
95
|
+
timestamp: start_ts
|
96
|
+
))
|
107
97
|
end
|
108
98
|
|
109
99
|
# Job failed with error event
|
110
|
-
sig { params(event:
|
100
|
+
sig { params(event: ::ActiveSupport::Notifications::Event).void }
|
111
101
|
def error(event)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
exception_executions:
|
122
|
-
|
123
|
-
error_message:
|
124
|
-
|
125
|
-
|
102
|
+
payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
|
103
|
+
job = payload[:job]
|
104
|
+
execution = payload[:execution] || payload[:good_job_execution]
|
105
|
+
exception = payload[:exception] || payload[:error]
|
106
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
107
|
+
base_fields = build_base_fields(job, payload)
|
108
|
+
|
109
|
+
logger.error(Log::GoodJob::Error.new(
|
110
|
+
**base_fields.to_kwargs,
|
111
|
+
exception_executions: execution&.exception_executions,
|
112
|
+
err_class: exception&.class&.name,
|
113
|
+
error_message: exception&.message,
|
114
|
+
backtrace: exception&.backtrace&.first(20),
|
115
|
+
duration_ms: event.duration.to_f,
|
126
116
|
process_id: ::Process.pid,
|
127
|
-
thread_id: Thread.current.object_id.to_s(36)
|
128
|
-
|
129
|
-
|
130
|
-
logger.error(log_entry)
|
117
|
+
thread_id: Thread.current.object_id.to_s(36),
|
118
|
+
timestamp: ts
|
119
|
+
))
|
131
120
|
end
|
132
121
|
|
133
122
|
# Job scheduled for future execution event
|
134
|
-
sig { params(event:
|
123
|
+
sig { params(event: ::ActiveSupport::Notifications::Event).void }
|
135
124
|
def schedule(event)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
)
|
150
|
-
|
151
|
-
logger.info(log_entry)
|
125
|
+
payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
|
126
|
+
job = payload[:job]
|
127
|
+
base_fields = build_base_fields(job, payload)
|
128
|
+
ts = event.time ? Time.at(event.time) : Time.now
|
129
|
+
|
130
|
+
logger.info(Log::GoodJob::Schedule.new(
|
131
|
+
**base_fields.to_kwargs,
|
132
|
+
scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
|
133
|
+
priority: job&.priority,
|
134
|
+
cron_key: job&.cron_key,
|
135
|
+
duration_ms: event.duration.to_f,
|
136
|
+
timestamp: ts
|
137
|
+
))
|
152
138
|
end
|
153
139
|
|
154
140
|
private
|
155
141
|
|
156
|
-
#
|
157
|
-
sig { params(
|
158
|
-
def
|
159
|
-
payload = event.payload || {}
|
160
|
-
job = payload[:job]
|
142
|
+
# Build BaseFields from job + payload (execution)
|
143
|
+
sig { params(job: T.untyped, payload: T::Hash[Symbol, T.untyped]).returns(Log::GoodJob::BaseFields) }
|
144
|
+
def build_base_fields(job, payload)
|
161
145
|
execution = payload[:execution] || payload[:good_job_execution]
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
data[:job_class] = job.job_class if job.respond_to?(:job_class)
|
170
|
-
data[:queue_name] = job.queue_name if job.respond_to?(:queue_name)
|
171
|
-
data[:arguments] = job.arguments if job.respond_to?(:arguments)
|
172
|
-
data[:priority] = job.priority if job.respond_to?(:priority)
|
173
|
-
data[:scheduled_at] = job.scheduled_at if job.respond_to?(:scheduled_at)
|
174
|
-
data[:cron_key] = job.cron_key if job.respond_to?(:cron_key)
|
175
|
-
data[:caller_location] = job.enqueue_caller_location if job.respond_to?(:enqueue_caller_location)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Execution-specific information
|
179
|
-
if execution
|
180
|
-
data[:executions] = execution.executions if execution.respond_to?(:executions)
|
181
|
-
data[:exception_executions] = execution.exception_executions if execution.respond_to?(:exception_executions)
|
182
|
-
# Use existing wait_time if available, otherwise calculate it
|
183
|
-
if execution.respond_to?(:wait_time) && execution.wait_time
|
184
|
-
data[:wait_time] = execution.wait_time
|
185
|
-
elsif execution.respond_to?(:created_at)
|
186
|
-
data[:wait_time] = calculate_wait_time(execution)
|
187
|
-
end
|
188
|
-
data[:batch_id] = execution.batch_id if execution.respond_to?(:batch_id)
|
189
|
-
data[:cron_key] ||= execution.cron_key if execution.respond_to?(:cron_key)
|
190
|
-
end
|
191
|
-
|
192
|
-
# Error information
|
193
|
-
if exception
|
194
|
-
data[:error_class] = exception.class.name
|
195
|
-
data[:error_message] = exception.message
|
196
|
-
data[:error_backtrace] = exception.backtrace&.first(20) # Limit backtrace size
|
197
|
-
end
|
198
|
-
|
199
|
-
# Result information
|
200
|
-
data[:result] = payload[:result] if payload.key?(:result)
|
201
|
-
|
202
|
-
data
|
146
|
+
Log::GoodJob::BaseFields.new(
|
147
|
+
job_id: job&.job_id,
|
148
|
+
job_class: job&.job_class,
|
149
|
+
queue_name: job&.queue_name,
|
150
|
+
arguments: job&.arguments,
|
151
|
+
executions: execution&.executions
|
152
|
+
)
|
203
153
|
end
|
204
154
|
|
205
155
|
# Calculate wait time from job creation to execution start
|
@@ -46,24 +46,22 @@ module LogStruct
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
# Emit a GoodJob::Log event with context and extra fields as additional_data
|
50
|
+
extras = {}
|
51
|
+
extras[:scheduled_at] = job_context[:scheduled_at] if job_context.key?(:scheduled_at)
|
52
|
+
extras[:priority] = job_context[:priority] if job_context.key?(:priority)
|
53
|
+
|
54
|
+
log_struct = Log::GoodJob::Log.new(
|
55
|
+
message: message || (block ? block.call : ""),
|
53
56
|
process_id: ::Process.pid,
|
54
57
|
thread_id: Thread.current.object_id.to_s(36),
|
55
58
|
job_id: job_context[:job_id],
|
56
59
|
job_class: job_context[:job_class],
|
57
60
|
queue_name: job_context[:queue_name],
|
58
61
|
executions: job_context[:executions],
|
59
|
-
|
60
|
-
priority: job_context[:priority],
|
61
|
-
additional_data: {
|
62
|
-
message: message || (block ? block.call : "")
|
63
|
-
}
|
62
|
+
additional_data: extras
|
64
63
|
)
|
65
64
|
|
66
|
-
# Pass the struct to SemanticLogger
|
67
65
|
super(log_struct, payload, &nil)
|
68
66
|
end
|
69
67
|
end
|
@@ -81,15 +81,13 @@ module LogStruct
|
|
81
81
|
# Configure error handling for thread errors if GoodJob supports it
|
82
82
|
if goodjob_module.respond_to?(:on_thread_error=)
|
83
83
|
goodjob_module.on_thread_error = ->(exception) do
|
84
|
-
|
85
|
-
|
86
|
-
event: Event::Error,
|
87
|
-
level: Level::Error,
|
88
|
-
error_class: exception.class.name,
|
84
|
+
log_entry = LogStruct::Log::GoodJob::Error.new(
|
85
|
+
err_class: exception.class.name,
|
89
86
|
error_message: exception.message,
|
90
|
-
|
87
|
+
backtrace: exception.backtrace,
|
88
|
+
process_id: ::Process.pid,
|
89
|
+
thread_id: Thread.current.object_id.to_s(36)
|
91
90
|
)
|
92
|
-
|
93
91
|
goodjob_module.logger.error(log_entry)
|
94
92
|
end
|
95
93
|
end
|
@@ -34,6 +34,23 @@ module LogStruct
|
|
34
34
|
return nil unless config.enabled
|
35
35
|
return nil unless config.integrations.enable_host_authorization
|
36
36
|
|
37
|
+
# In test environment, ensure HostAuthorization does not block requests
|
38
|
+
# from the default integration test hosts. Allow all hosts explicitly.
|
39
|
+
if ::Rails.env.test? && ::Rails.application.config.respond_to?(:hosts)
|
40
|
+
begin
|
41
|
+
::Rails.application.config.hosts << /.*\z/
|
42
|
+
rescue
|
43
|
+
# best-effort; ignore if hosts not configurable
|
44
|
+
end
|
45
|
+
# Additionally, exclude all requests from HostAuthorization in test
|
46
|
+
begin
|
47
|
+
::Rails.application.config.host_authorization ||= {}
|
48
|
+
::Rails.application.config.host_authorization[:exclude] = ->(_request) { true }
|
49
|
+
rescue
|
50
|
+
# best-effort
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
37
54
|
# Define the response app as a separate variable to fix block alignment
|
38
55
|
response_app = lambda do |env|
|
39
56
|
request = ::ActionDispatch::Request.new(env)
|
@@ -69,10 +86,14 @@ module LogStruct
|
|
69
86
|
[FORBIDDEN_STATUS, RESPONSE_HEADERS, [RESPONSE_HTML]]
|
70
87
|
end
|
71
88
|
|
72
|
-
#
|
73
|
-
Rails.application.config.host_authorization
|
74
|
-
|
75
|
-
|
89
|
+
# Merge our response_app into existing host_authorization config to preserve excludes
|
90
|
+
existing = Rails.application.config.host_authorization
|
91
|
+
unless existing.is_a?(Hash)
|
92
|
+
existing = {}
|
93
|
+
end
|
94
|
+
existing = existing.dup
|
95
|
+
existing[:response_app] = response_app
|
96
|
+
Rails.application.config.host_authorization = existing
|
76
97
|
|
77
98
|
true
|
78
99
|
end
|