logstruct 0.0.2 → 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 +4 -22
- data/README.md +25 -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/concerns/logging.rb +5 -5
- data/lib/log_struct/config_struct/filters.rb +18 -0
- data/lib/log_struct/config_struct/integrations.rb +16 -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 +49 -0
- 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 +54 -0
- 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 +25 -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 +94 -0
- 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 +88 -0
- 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 -46
- 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 +4 -0
- 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 -25
- data/lib/log_struct/log/shared/serialize_common.rb +1 -33
- data/lib/log_struct/log/shared/serialize_common_public.rb +44 -0
- 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 +31 -32
- 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 +3 -0
- metadata +108 -5
- data/lib/log_struct/log/interfaces/message_field.rb +0 -20
- data/lib/log_struct/log_keys.rb +0 -102
@@ -38,7 +38,7 @@ module LogStruct
|
|
38
38
|
# Extract key information from the event
|
39
39
|
event_name = event.name.sub(/\.active_storage$/, "")
|
40
40
|
service_name = event.payload[:service]
|
41
|
-
|
41
|
+
duration_ms = event.duration
|
42
42
|
|
43
43
|
# Map service events to log event types
|
44
44
|
event_type = case event_name
|
@@ -65,26 +65,65 @@ module LogStruct
|
|
65
65
|
end
|
66
66
|
|
67
67
|
# Map the event name to an operation
|
68
|
-
|
68
|
+
event_name.sub(/^service_/, "").to_sym
|
69
69
|
|
70
|
-
# Create structured log event
|
71
|
-
log_data =
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
70
|
+
# Create structured log event using generated classes
|
71
|
+
log_data = case event_type
|
72
|
+
when Event::Upload
|
73
|
+
Log::ActiveStorage::Upload.new(
|
74
|
+
storage: service_name.to_s,
|
75
|
+
file_id: event.payload[:key]&.to_s,
|
76
|
+
checksum: event.payload[:checksum]&.to_s,
|
77
|
+
duration_ms: duration_ms,
|
78
|
+
metadata: event.payload[:metadata],
|
79
|
+
filename: event.payload[:filename],
|
80
|
+
mime_type: event.payload[:content_type],
|
81
|
+
size: event.payload[:byte_size]
|
82
|
+
)
|
83
|
+
when Event::Download
|
84
|
+
Log::ActiveStorage::Download.new(
|
85
|
+
storage: service_name.to_s,
|
86
|
+
file_id: event.payload[:key]&.to_s,
|
87
|
+
filename: event.payload[:filename],
|
88
|
+
range: event.payload[:range],
|
89
|
+
duration_ms: duration_ms
|
90
|
+
)
|
91
|
+
when Event::Delete
|
92
|
+
Log::ActiveStorage::Delete.new(
|
93
|
+
storage: service_name.to_s,
|
94
|
+
file_id: event.payload[:key]&.to_s
|
95
|
+
)
|
96
|
+
when Event::Metadata
|
97
|
+
Log::ActiveStorage::Metadata.new(
|
98
|
+
storage: service_name.to_s,
|
99
|
+
file_id: event.payload[:key]&.to_s,
|
100
|
+
metadata: event.payload[:metadata]
|
101
|
+
)
|
102
|
+
when Event::Exist
|
103
|
+
Log::ActiveStorage::Exist.new(
|
104
|
+
storage: service_name.to_s,
|
105
|
+
file_id: event.payload[:key]&.to_s,
|
106
|
+
exist: event.payload[:exist]
|
107
|
+
)
|
108
|
+
when Event::Stream
|
109
|
+
Log::ActiveStorage::Stream.new(
|
110
|
+
storage: service_name.to_s,
|
111
|
+
file_id: event.payload[:key]&.to_s,
|
112
|
+
prefix: event.payload[:prefix]
|
113
|
+
)
|
114
|
+
when Event::Url
|
115
|
+
Log::ActiveStorage::Url.new(
|
116
|
+
storage: service_name.to_s,
|
117
|
+
file_id: event.payload[:key]&.to_s,
|
118
|
+
url: event.payload[:url]
|
119
|
+
)
|
120
|
+
else
|
121
|
+
Log::ActiveStorage::Metadata.new(
|
122
|
+
storage: service_name.to_s,
|
123
|
+
file_id: event.payload[:key]&.to_s,
|
124
|
+
metadata: event.payload[:metadata]
|
125
|
+
)
|
126
|
+
end
|
88
127
|
|
89
128
|
# Log structured data
|
90
129
|
LogStruct.info(log_data)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module LogStruct
|
5
|
+
module Integrations
|
6
|
+
# Ahoy analytics integration. If Ahoy is present, prepend a small hook to
|
7
|
+
# Ahoy::Tracker#track to emit a structured log for analytics events.
|
8
|
+
module Ahoy
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(config: LogStruct::Configuration).returns(T.nilable(TrueClass)) }
|
12
|
+
def self.setup(config)
|
13
|
+
return nil unless defined?(::Ahoy)
|
14
|
+
|
15
|
+
if defined?(::Ahoy::Tracker)
|
16
|
+
mod = Module.new do
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig { params(name: T.untyped, properties: T.nilable(T::Hash[T.untyped, T.untyped]), options: T.untyped).returns(T.untyped) }
|
20
|
+
def track(name, properties = nil, options = nil)
|
21
|
+
result = super
|
22
|
+
begin
|
23
|
+
# Emit a lightweight structured log about the analytics event
|
24
|
+
data = {
|
25
|
+
ahoy_event: T.let(name, T.untyped)
|
26
|
+
}
|
27
|
+
data[:properties] = properties if properties
|
28
|
+
LogStruct.info(
|
29
|
+
LogStruct::Log::Ahoy.new(
|
30
|
+
message: "ahoy.track",
|
31
|
+
ahoy_event: T.must(T.let(name, T.nilable(String))),
|
32
|
+
properties: T.let(
|
33
|
+
properties && properties.transform_keys { |k| k.to_sym },
|
34
|
+
T.nilable(T::Hash[Symbol, T.untyped])
|
35
|
+
),
|
36
|
+
additional_data: {}
|
37
|
+
)
|
38
|
+
)
|
39
|
+
rescue => e
|
40
|
+
# Never raise from logging; rely on global error handling policies
|
41
|
+
LogStruct.handle_exception(e, source: LogStruct::Source::App, context: {integration: :ahoy})
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
T.unsafe(::Ahoy::Tracker).prepend(mod)
|
48
|
+
end
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -52,18 +52,16 @@ module LogStruct
|
|
52
52
|
}
|
53
53
|
|
54
54
|
# Log the store operation with structured data
|
55
|
-
log_data = Log::CarrierWave.new(
|
56
|
-
source: Source::CarrierWave,
|
57
|
-
event: Event::Upload,
|
58
|
-
duration: duration * 1000.0, # Convert to ms
|
59
|
-
model: model.class.name,
|
60
|
-
uploader: self.class.name,
|
55
|
+
log_data = Log::CarrierWave::Upload.new(
|
61
56
|
storage: storage.class.name,
|
62
|
-
|
57
|
+
file_id: identifier,
|
63
58
|
filename: file.filename,
|
64
59
|
mime_type: file.content_type,
|
65
60
|
size: file_size,
|
66
|
-
|
61
|
+
duration_ms: (duration * 1000.0).to_f,
|
62
|
+
uploader: self.class.name,
|
63
|
+
model: model.class.name,
|
64
|
+
mount_point: mounted_as.to_s,
|
67
65
|
additional_data: {
|
68
66
|
version: version_name.to_s,
|
69
67
|
store_path: store_path,
|
@@ -78,25 +76,24 @@ module LogStruct
|
|
78
76
|
# Log file retrieve operations
|
79
77
|
sig { params(identifier: T.untyped, args: T.untyped).returns(T.untyped) }
|
80
78
|
def retrieve_from_store!(identifier, *args)
|
81
|
-
|
79
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
82
80
|
result = super
|
83
|
-
|
81
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
84
82
|
|
85
83
|
# Extract file information if available
|
86
84
|
file_size = file.size if file&.respond_to?(:size)
|
87
85
|
|
88
86
|
# Log the retrieve operation with structured data
|
89
|
-
log_data = Log::CarrierWave.new(
|
90
|
-
source: Source::CarrierWave,
|
91
|
-
event: Event::Download,
|
92
|
-
duration: duration * 1000.0, # Convert to ms
|
93
|
-
uploader: self.class.name,
|
87
|
+
log_data = Log::CarrierWave::Download.new(
|
94
88
|
storage: storage.class.name,
|
95
|
-
mount_point: mounted_as.to_s,
|
96
89
|
file_id: identifier,
|
97
90
|
filename: file&.filename,
|
98
91
|
mime_type: file&.content_type,
|
99
92
|
size: file_size,
|
93
|
+
# No duration field on Download event schema
|
94
|
+
uploader: self.class.name,
|
95
|
+
model: model.class.name,
|
96
|
+
mount_point: mounted_as.to_s,
|
100
97
|
additional_data: {
|
101
98
|
version: version_name.to_s
|
102
99
|
}
|
@@ -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
|