lib/semantic_logger/logger.rb in semantic_logger-0.7.1 vs lib/semantic_logger/logger.rb in semantic_logger-0.8.0

- old
+ new

@@ -1,6 +1,7 @@ require 'thread' +require 'thread_safe' require 'sync_attr' # Logger is the interface used by # # Logger maintains the logging name to be used for all log entries generated @@ -34,13 +35,18 @@ # module SemanticLogger class Logger < Base include SyncAttr - # Thread safe Class Attribute accessor for appenders array - sync_cattr_accessor :appenders do - [] + # Add or remove logging appenders to the appenders Array + # Appenders will be written to in the order that they appear in this list + sync_cattr_reader :appenders do + # The logging thread is only started once an appender has been defined + startup + + # Thread safe appenders array + ThreadSafe::Array.new end # Initial default Level for all new instances of SemanticLogger::Logger @@default_level = :info @@ -88,21 +94,21 @@ # look into speeding up the appenders themselves def self.cache_count queue.size end - # Flush all pending log entry disk, database, etc. - # All pending log writes are completed and each appender is flushed in turn + # 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 - return false unless @@appender_thread.alive? + return false unless started? reply_queue = Queue.new queue << { :command => :flush, :reply_queue => reply_queue } reply_queue.pop end - @@lag_check_interval = 1000 + @@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 @@ -123,17 +129,29 @@ def self.time_threshold_s=(time_threshold_s) @@lag_threshold_s = time_threshold_s end + # Returns whether the logging thread has been started + def self.started? + defined? :@@appenders + end + ############################################################################ protected + @@queue = Queue.new + + # Queue to hold messages that need to be logged to the various appenders + def self.queue + @@queue + 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) - self.class.queue << log + self.class.queue << log if self.class.started? end # Internal logger for SemanticLogger # For example when an appender is not working etc.. # By default logs to STDERR @@ -144,70 +162,68 @@ # within Semantic Logger itself sync_cattr_accessor :logger do SemanticLogger::Appender::File.new(STDERR, :warn) end - # Log to queue - # Starts the appender thread the first time a logging call is made - sync_cattr_reader :queue do - startup - at_exit { shutdown } - Queue.new - end - # Start a separate appender thread responsible for reading log messages and # calling the appenders in it's thread def self.startup - @@appender_thread = Thread.new do + # 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.new do + logger.debug "SemanticLogger::Logger Appender thread started" begin - logger.debug "SemanticLogger::Logger Appender thread started" count = 0 while message = queue.pop if message.is_a? Log - appenders.each {|appender| appender.log(message) } + appenders.each do |appender| + begin + appender.log(message) + rescue Exception => exc + logger.error "SemanticLogger::Logger Appender thread: Failed to log to appender: #{appender.inspect}", exc + end + end 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 "SemanticLogger::Logger Appender thread has fallen behind by #{diff} seconds with #{cache_count} messages queued up. Consider reducing the log level or changing the appenders" end count = 0 end else case message[:command] - when :shutdown - appenders.each {|appender| appender.flush } - message[:reply_queue] << true if message[:reply_queue] - logger.debug "SemanticLogger::Logger appenders flushed, now shutting down" - break when :flush - appenders.each {|appender| appender.flush } + appenders.each do |appender| + begin + appender.flush + rescue Exception => exc + logger.error "SemanticLogger::Logger Appender thread: Failed to flush appender: #{appender.inspect}", exc + end + end + message[:reply_queue] << true if message[:reply_queue] logger.debug "SemanticLogger::Logger appenders flushed" + else + logger.warn "SemanticLogger::Logger Appender thread: Ignoring unknown command: #{message[:command]}" end end end rescue Exception => exception - logger.error "SemanticLogger::Logger Appender thread restarting due to exception: #{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}" - # Start a new appender thread and let this one terminate - startup + logger.error "SemanticLogger::Logger Appender thread restarting due to exception", exception + retry ensure logger.debug "SemanticLogger::Logger Appender thread stopped" end end - end - # Stop the log appender thread and flush all appenders - def self.shutdown - return false unless @@appender_thread.alive? - - logger.debug "SemanticLogger::Logger Shutdown. Stopping appender thread" - queue << { :command => :shutdown } - @@appender_thread.join - # Undefine the class variable for the queue since in test environments - # at_exit can be invoked multiple times - remove_class_variable(:@@queue) - true + at_exit do + logger.debug "SemanticLogger::Logger process terminating, flushing appenders" + flush + end end # Formatting does not occur within this thread, it is done by each appender # in the appender thread def default_formatter