Module: LogStruct::Integrations::Dotenv
- Extended by:
- IntegrationInterface, T::Sig
- Defined in:
- lib/log_struct/integrations/dotenv.rb
Overview
Dotenv integration: emits structured logs for load/update/save/restore events
Defined Under Namespace
Classes: State
Constant Summary collapse
- STATE =
T.let(State.new(false), State)
- @@boot_subscribed =
Early boot subscription to buffer structured logs until logger is ready
T.let(false, T::Boolean)
Class Method Summary collapse
-
.intercept_logger_setter! ⇒ void
Intercept Dotenv::Rails#logger= to defer replay until we resolve policy.
-
.resolve_boot_logs! ⇒ void
Decide which boot logs to emit after user initializers.
- .setup(config) ⇒ Boolean?
- .setup_boot ⇒ void
- .subscribe! ⇒ void
Methods included from IntegrationInterface
Class Method Details
.intercept_logger_setter! ⇒ void
This method returns an undefined value.
Intercept Dotenv::Rails#logger= to defer replay until we resolve policy
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/log_struct/integrations/dotenv.rb', line 186 def self.intercept_logger_setter! return unless Object.const_defined?(:Dotenv) # Do not intercept when LogStruct is disabled; allow original dotenv replay return unless LogStruct.enabled? dotenv_mod = T.unsafe(Object.const_get(:Dotenv)) return unless dotenv_mod.const_defined?(:Rails) klass = T.unsafe(dotenv_mod.const_get(:Rails)) return if klass.instance_variable_defined?(:@_logstruct_replay_patched) original = klass.instance_method(:logger=) @original_logger_setter = original mod = Module.new do define_method :logger= do |new_logger| # Defer replay: store desired logger, keep ReplayLogger as current instance_variable_set(:@logstruct_pending_dotenv_logger, new_logger) new_logger end define_method :logstruct_pending_dotenv_logger do instance_variable_get(:@logstruct_pending_dotenv_logger) end end klass.prepend(mod) klass.instance_variable_set(:@_logstruct_replay_patched, true) end |
.resolve_boot_logs! ⇒ void
This method returns an undefined value.
Decide which boot logs to emit after user initializers
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/log_struct/integrations/dotenv.rb', line 216 def self.resolve_boot_logs! # If LogStruct is disabled, do not alter dotenv behavior at all return unless LogStruct.enabled? dotenv_mod = Object.const_defined?(:Dotenv) ? T.unsafe(Object.const_get(:Dotenv)) : nil klass = dotenv_mod&.const_defined?(:Rails) ? T.unsafe(dotenv_mod.const_get(:Rails)) : nil pending_logger = nil railtie_instance = nil if klass&.respond_to?(:instance) railtie_instance = klass.instance if railtie_instance.respond_to?(:logstruct_pending_dotenv_logger) pending_logger = T.unsafe(railtie_instance).logstruct_pending_dotenv_logger end end if LogStruct.enabled? && LogStruct.config.integrations.enable_dotenv # Structured path if pending_logger && railtie_instance # Clear any buffered original logs current_logger = railtie_instance.logger if railtie_instance.respond_to?(:logger) if current_logger && current_logger.class.name.end_with?("ReplayLogger") begin logs = current_logger.instance_variable_get(:@logs) logs.clear if logs.respond_to?(:clear) rescue # best effort end end railtie_instance.config.dotenv.logger = pending_logger end # Detach original subscriber and subscribe runtime structured if dotenv_mod&.const_defined?(:LogSubscriber) T.unsafe(dotenv_mod.const_get(:LogSubscriber)).detach_from(:dotenv) end LogStruct::Integrations::Dotenv.subscribe! require_relative "../boot_buffer" LogStruct::BootBuffer.flush else # Original path: replay dotenv lines, drop structured buffer if railtie_instance && @original_logger_setter setter = @original_logger_setter new_logger = pending_logger if new_logger.nil? && ENV["RAILS_LOG_TO_STDOUT"].to_s.strip != "" require "logger" require "active_support/tagged_logging" new_logger = ActiveSupport::TaggedLogging.new(::Logger.new($stdout)).tagged("dotenv") end setter.bind_call(railtie_instance, new_logger) if new_logger end require_relative "../boot_buffer" LogStruct::BootBuffer.clear end end |
.setup(config) ⇒ Boolean?
27 28 29 30 31 32 |
# File 'lib/log_struct/integrations/dotenv.rb', line 27 def self.setup(config) # Subscribe regardless of dotenv gem presence so instrumentation via # ActiveSupport::Notifications can be captured during tests and runtime. subscribe! true end |
.setup_boot ⇒ void
This method returns an undefined value.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/log_struct/integrations/dotenv.rb', line 123 def self.setup_boot return if @@boot_subscribed return unless defined?(::ActiveSupport::Notifications) instrumenter = if Object.const_defined?(:Dotenv) dm = T.unsafe(Object.const_get(:Dotenv)) dm.respond_to?(:instrumenter) ? T.unsafe(dm).instrumenter : ::ActiveSupport::Notifications else ::ActiveSupport::Notifications end instrumenter.subscribe("load.dotenv") do |*args| event = ::ActiveSupport::Notifications::Event.new(*args) env = event.payload[:env] abs = env.filename file = begin if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root Pathname.new(abs).relative_path_from(Pathname.new(::Rails.root.to_s)).to_s else abs end rescue abs end ts = event.time ? Time.at(event.time) : Time.now LogStruct::BootBuffer.add(Log::Dotenv::Load.new(file: file, timestamp: ts)) rescue => e LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end instrumenter.subscribe("update.dotenv") do |*args| event = ::ActiveSupport::Notifications::Event.new(*args) diff = event.payload[:diff] vars = diff.env.keys.map(&:to_s) ts = event.time ? Time.at(event.time) : Time.now LogStruct::BootBuffer.add(Log::Dotenv::Update.new(vars: vars, timestamp: ts)) rescue => e LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end instrumenter.subscribe("save.dotenv") do |*args| event = ::ActiveSupport::Notifications::Event.new(*args) ts = event.time ? Time.at(event.time) : Time.now LogStruct::BootBuffer.add(Log::Dotenv::Save.new(snapshot: true, timestamp: ts)) rescue => e LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end instrumenter.subscribe("restore.dotenv") do |*args| event = ::ActiveSupport::Notifications::Event.new(*args) diff = event.payload[:diff] vars = diff.env.keys.map(&:to_s) ts = event.time ? Time.at(event.time) : Time.now LogStruct::BootBuffer.add(Log::Dotenv::Restore.new(vars: vars, timestamp: ts)) rescue => e LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end @@boot_subscribed = true end |
.subscribe! ⇒ void
This method returns an undefined value.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/log_struct/integrations/dotenv.rb', line 38 def subscribe! # Guard against double subscription return if STATE.subscribed instrumenter = defined?(::ActiveSupport::Notifications) ? ::ActiveSupport::Notifications : nil return unless instrumenter instrumenter.subscribe("load.dotenv") do |*args| # Allow tests to stub Log::Dotenv.new to force an error path LogStruct::Log::Dotenv.new event = ::ActiveSupport::Notifications::Event.new(*args) env = event.payload[:env] abs = env.filename file = begin if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root Pathname.new(abs).relative_path_from(Pathname.new(::Rails.root.to_s)).to_s else abs end rescue abs end ts = event.time ? Time.at(event.time) : Time.now LogStruct.info(Log::Dotenv::Load.new(file: file, timestamp: ts)) rescue => e if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test" raise else LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end end instrumenter.subscribe("update.dotenv") do |*args| LogStruct::Log::Dotenv.new event = ::ActiveSupport::Notifications::Event.new(*args) diff = event.payload[:diff] vars = diff.env.keys.map(&:to_s) ts = event.time ? Time.at(event.time) : Time.now LogStruct.debug(Log::Dotenv::Update.new(vars: vars, timestamp: ts)) rescue => e if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test" raise else LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end end instrumenter.subscribe("save.dotenv") do |*args| LogStruct::Log::Dotenv.new event = ::ActiveSupport::Notifications::Event.new(*args) ts = event.time ? Time.at(event.time) : Time.now LogStruct.info(Log::Dotenv::Save.new(snapshot: true, timestamp: ts)) rescue => e if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test" raise else LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end end instrumenter.subscribe("restore.dotenv") do |*args| LogStruct::Log::Dotenv.new event = ::ActiveSupport::Notifications::Event.new(*args) diff = event.payload[:diff] vars = diff.env.keys.map(&:to_s) ts = event.time ? Time.at(event.time) : Time.now LogStruct.info(Log::Dotenv::Restore.new(vars: vars, timestamp: ts)) rescue => e if defined?(::Rails) && ::Rails.respond_to?(:env) && ::Rails.env == "test" raise else LogStruct.handle_exception(e, source: LogStruct::Source::Dotenv) end end STATE.subscribed = true end |