lib/semantic_logger/processor.rb in semantic_logger-4.0.0 vs lib/semantic_logger/processor.rb in semantic_logger-4.1.0

- old
+ new

@@ -2,28 +2,47 @@ # Thread that submits and processes log requests class Processor # Start the appender thread def self.start return false if active? - @@thread = Thread.new { process_requests } - raise 'Failed to start Appender Thread' unless @@thread + @thread = Thread.new { process_requests } + raise 'Failed to start Appender Thread' unless @thread true end # Returns true if the appender_thread is active def self.active? - @@thread && @@thread.alive? + @thread && @thread.alive? end - # Returns [Integer] the number of log entries waiting to be written to the appenders + # Returns [Integer] the number of log entries waiting to be written to the appenders. + # + # When this number grows it is because the logging appender thread is not + # able to write to the appenders fast enough. Either reduce the amount of + # logging, increase the log level, reduce the number of appenders, or + # look into speeding up the appenders themselves def self.queue_size queue.size end - # Add log request to the queue for processing + # Flush all queued log entries disk, database, etc. + # All queued log messages are written and then each appender is flushed in turn. + def self.flush + submit_request(:flush) + end + + # Close all appenders and flush any outstanding messages. + def self.close + submit_request(:close) + end + + # Add log request to the queue for processing. def self.<<(log) - queue << log if active? + return unless active? + + call_log_subscribers(log) + queue << log end # Submit command and wait for reply def self.submit_request(command) return false unless active? @@ -45,82 +64,72 @@ # Allow the internal logger to be overridden from its default of STDERR # Can be replaced with another Ruby logger or Rails logger, but never to # SemanticLogger::Logger itself since it is for reporting problems # while trying to log to the various appenders def self.logger=(logger) - @@logger = logger + @logger = logger end # Returns the check_interval which is the number of messages between checks # to determine if the appender thread is falling behind def self.lag_check_interval - @@lag_check_interval + @lag_check_interval end # Set the check_interval which is the number of messages between checks # to determine if the appender thread is falling behind def self.lag_check_interval=(lag_check_interval) - @@lag_check_interval = lag_check_interval + @lag_check_interval = lag_check_interval end # Returns the amount of time in seconds # to determine if the appender thread is falling behind def self.lag_threshold_s - @@lag_threshold_s + @lag_threshold_s end - # Supply a metrics appender to be called whenever a logging metric is encountered - # - # Parameters - # appender: [Symbol | Object | Proc] - # [Proc] the block to call. - # [Object] the block on which to call #call. - # [Symbol] :new_relic, or :statsd to forward metrics to - # - # block - # The block to be called - # - # Example: - # SemanticLogger.on_metric do |log| - # puts "#{log.metric} was received. Log: #{log.inspect}" - # end - # - # Note: - # * This callback is called in the logging thread. - # * Does not slow down the application. - # * Only context is what is passed in the log struct, the original thread context is not available. def self.on_metric(options = {}, &block) # Backward compatibility - options = options.is_a?(Hash) ? options.dup : {appender: options} - appender = block || options.delete(:appender) + options = options.is_a?(Hash) ? options.dup : {appender: options} + subscriber = block || options.delete(:appender) # Convert symbolized metrics appender to an actual object - appender = SemanticLogger::Appender.constantize_symbol(appender, 'SemanticLogger::Metrics').new(options) if appender.is_a?(Symbol) + subscriber = SemanticLogger::Appender.constantize_symbol(subscriber, 'SemanticLogger::Metrics').new(options) if subscriber.is_a?(Symbol) - raise('When supplying a metrics appender, it must support the #call method') unless appender.is_a?(Proc) || appender.respond_to?(:call) - (@@metric_subscribers ||= Concurrent::Array.new) << appender + raise('When supplying a metrics subscriber, it must support the #call method') unless subscriber.is_a?(Proc) || subscriber.respond_to?(:call) + subscribers = (@metric_subscribers ||= Concurrent::Array.new) + subscribers << subscriber unless subscribers.include?(subscriber) end + def self.on_log(object = nil, &block) + subscriber = block || object + + raise('When supplying an on_log subscriber, it must support the #call method') unless subscriber.is_a?(Proc) || subscriber.respond_to?(:call) + subscribers = (@log_subscribers ||= Concurrent::Array.new) + subscribers << subscriber unless subscribers.include?(subscriber) + end + private - @@thread = nil - @@queue = Queue.new - @@lag_check_interval = 5000 - @@lag_threshold_s = 30 - @@metric_subscribers = nil + @thread = nil + @queue = Queue.new + @lag_check_interval = 5000 + @lag_threshold_s = 30 + @metric_subscribers = nil + @log_subscribers = nil # Queue to hold messages that need to be logged to the various appenders def self.queue - @@queue + @queue end # Internal logger for SemanticLogger # For example when an appender is not working etc.. # By default logs to STDERR def self.logger - @@logger ||= begin - l = SemanticLogger::Appender::File.new(STDERR, :warn) + @logger ||= begin + l = SemanticLogger::Appender::File.new(io: STDERR, level: :warn) l.name = name l end end @@ -154,10 +163,11 @@ flush_appenders message[:reply_queue] << true if message[:reply_queue] when :close close_appenders message[:reply_queue] << true if message[:reply_queue] + break else logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}" end end end @@ -168,11 +178,11 @@ rescue Exception nil end retry ensure - @@thread = nil + @thread = nil # This block may be called after the file handles have been released by Ruby begin logger.trace 'Appender thread has stopped' rescue Exception nil @@ -181,16 +191,30 @@ end # Call Metric subscribers def self.call_metric_subscribers(log) # If no subscribers registered, then return immediately - return unless @@metric_subscribers + return unless @metric_subscribers - @@metric_subscribers.each do |subscriber| + @metric_subscribers.each do |subscriber| begin subscriber.call(log) rescue Exception => exc logger.error 'Exception calling metrics subscriber', exc + end + end + end + + # Call on_log subscribers + def self.call_log_subscribers(log) + # If no subscribers registered, then return immediately + return unless @log_subscribers + + @log_subscribers.each do |subscriber| + begin + subscriber.call(log) + rescue Exception => exc + logger.error 'Exception calling :on_log subscriber', exc end end end # Call Appenders