module Semantic # use the Zeitwerk autoloader to reattach_appender for development autoreloading feature class DevLoader def initialize(session_key) @session_key = session_key force_preload_module once_and_reload do append_ansi_formatter register_log_subscriber end # FIXME: proper unsubscribe!!! # RailsSemanticLogger::ActionController::LogSubscriber.logger.level = :fatal # # RailsSemanticLogger.swap_subscriber( # RailsSemanticLogger::ActionController::LogSubscriber, # @log_subscriber, # attach missing!!! # :action_controller # ) # ActiveSupport::LogSubscriber.subscribers.each do |sub| # puts "subscriber #{sub.pattern}" # end # RailsSemanticLogger.swap_subscriber(RailsSemanticLogger::ActionController::LogSubscriber, # @log_subscriber, :action_controller) end private def once_and_reload(&) yield Rails.autoloaders.main.on_load('ApplicationController', &) end def force_preload_module self.class.module_parent.constants.each { |const| self.class.module_parent.const_get(const) } end def append_ansi_formatter SemanticLogger.clear_appenders! formatter = Semantic::AnsiFormatter.new SemanticLogger.add_appender(io: $stdout, formatter:, filter: ->(log) { !formatter.reject(log) }) end def register_log_subscriber reset_subscribers register_to_action_controller(:start_processing) register_to_action_controller(:process_action, :finish_processing) register_to_action_controller(:redirect_to) %i[send_file send_data halted_callback unpermitted_parameters send_stream write_fragment read_fragment expire_fragment exist_fragment?].each do |hook| register_to_action_controller(hook, :any_hook) end end def register_to_action_controller(hook, method = hook) @subscribers << ActiveSupport::Notifications.subscribe("#{hook}.action_controller") do |event| @log_subscriber.send(method, event) end end def subscriber_patterns(subscriber) subscriber.patterns.respond_to?(:keys) ? subscriber.patterns.keys : subscriber.patterns end def unattach(subscriber, pattern) subscriber_patterns(subscriber).each do |sub_pattern| ActiveSupport::Notifications.notifier.listeners_for(sub_pattern).each do |sub| next unless sub.instance_variable_get(:@delegate) == subscriber next unless pattern.match(sub_pattern) # puts "FOUND subscriber=#{subscriber} for sub_pattern=#{sub_pattern} with logger #{subscriber.logger.name}" # puts 'remove notification then log_subscriber' ActiveSupport::Notifications.unsubscribe(sub) ActiveSupport::LogSubscriber.subscribers.delete(subscriber) end end end # pattern could be either a string 'start_processing.action_controller' or a regex /\.action_controller$/ def clear_subscribers(pattern) ActiveSupport::LogSubscriber.subscribers.each { |sub| unattach(sub, pattern) } end def reset_subscribers clear_subscribers(/\.action_controller$/) if defined?(@subscribers) @subscribers.each { |sub| ActiveSupport::Notifications.unsubscribe(sub) } @subscribers.clear else @subscribers = [] end @log_subscriber = Semantic::LogSubscriber.new(@session_key) end end end