lib/semantic_logger/logger.rb in semantic_logger-3.4.1 vs lib/semantic_logger/logger.rb in semantic_logger-4.0.0.beta1

- old
+ new

@@ -36,246 +36,37 @@ # 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 + Processor.queue_size end # 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 - msg = "Flushing appenders with #{queue_size} log messages on the queue" - if queue_size > 1_000 - logger.warn msg - elsif queue_size > 100 - logger.info msg - elsif queue_size > 0 - logger.trace msg - end - process_request(:flush) + Processor.submit_request(:flush) end # Close all appenders and flush any outstanding messages def self.close - msg = "Closing appenders with #{queue_size} log messages on the queue" - if queue_size > 1_000 - logger.warn msg - elsif queue_size > 100 - logger.info msg - elsif queue_size > 0 - logger.trace msg - end - process_request(:close) + Processor.submit_request(:close) end - @@lag_check_interval = 5000 - @@lag_threshold_s = 30 - - # 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 - 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 - 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 - end - - # Allow the internal logger to be overridden from its default to STDERR + # 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 + Processor.logger = logger 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 Struct: #{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) - - # Convert symbolized metrics appender to an actual object - appender = SemanticLogger.constantize_symbol(appender, 'SemanticLogger::Metrics').new(options) if appender.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 - end - # Place log request on the queue for the Appender thread to write to each # appender in the order that they were registered def log(log, message = nil, progname = nil, &block) # Compatibility with ::Logger return add(log, message, progname, &block) unless log.is_a?(SemanticLogger::Log) - self.class.queue << log if @@appender_thread - end - - private - - @@appender_thread = nil - @@queue = Queue.new - @@metric_subscribers = nil - - # Queue to hold messages that need to be logged to the various appenders - def self.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) - l.name = name - l - end - end - - # Start the appender thread - def self.start_appender_thread - return false if appender_thread_active? - @@appender_thread = Thread.new { appender_thread } - raise 'Failed to start Appender Thread' unless @@appender_thread - true - end - - # Returns true if the appender_thread is active - def self.appender_thread_active? - @@appender_thread && @@appender_thread.alive? - end - - # Separate appender thread responsible for reading log messages and - # calling the appenders in it's thread - def self.appender_thread - # This thread is designed to never go down unless the main thread terminates - # Before terminating at_exit is used to flush all the appenders - # - # Should any appender fail to log or flush, the exception is logged and - # other appenders will still be called - Thread.current.name = 'SemanticLogger::AppenderThread' - logger.trace "V#{VERSION} Appender thread active" - begin - count = 0 - while message = queue.pop - if message.is_a?(Log) - SemanticLogger.appenders.each do |appender| - begin - appender.log(message) - rescue Exception => exc - logger.error "Appender thread: Failed to log to appender: #{appender.inspect}", exc - end - end - call_metric_subscribers(message) if message.metric - count += 1 - # Check every few log messages whether this appender thread is falling behind - if count > lag_check_interval - if (diff = Time.now - message.time) > lag_threshold_s - logger.warn "Appender thread has fallen behind by #{diff} seconds with #{queue_size} messages queued up. Consider reducing the log level or changing the appenders" - end - count = 0 - end - else - case message[:command] - when :flush - SemanticLogger.appenders.each do |appender| - begin - logger.trace "Appender thread: Flushing appender: #{appender.name}" - appender.flush - rescue Exception => exc - logger.error "Appender thread: Failed to flush appender: #{appender.inspect}", exc - end - end - - message[:reply_queue] << true if message[:reply_queue] - logger.trace 'Appender thread: All appenders flushed' - when :close - SemanticLogger.appenders.each do |appender| - begin - logger.trace "Appender thread: Closing appender: #{appender.name}" - appender.flush - appender.close - SemanticLogger.remove_appender(appender) - rescue Exception => exc - logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc - end - end - - message[:reply_queue] << true if message[:reply_queue] - logger.trace 'Appender thread: All appenders flushed' - else - logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}" - end - end - end - rescue Exception => exception - # This block may be called after the file handles have been released by Ruby - begin - logger.error 'Appender thread restarting due to exception', exception - rescue Exception - nil - end - retry - ensure - @@appender_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 - end - end - end - - # Call Metric subscribers - def self.call_metric_subscribers(log) - # If no subscribers registered, then return immediately - return unless @@metric_subscribers - - @@metric_subscribers.each do |subscriber| - begin - subscriber.call(log) - rescue Exception => exc - logger.error 'Exception calling metrics subscriber', exc - end - end - end - - # Close all appenders and flush any outstanding messages - def self.process_request(command) - return false unless appender_thread_active? - - reply_queue = Queue.new - queue << {command: command, reply_queue: reply_queue} - reply_queue.pop + Processor << log end end end