lib/semantic_logger/logger.rb in semantic_logger-0.0.2 vs lib/semantic_logger/logger.rb in semantic_logger-0.1.0

- old
+ new

@@ -24,12 +24,12 @@ # logger.debug("Login time", :user => 'Mary', :duration => 230, :ip_address=>'192.168.0.1') module SemanticLogger class Logger include SyncAttr - # Logging levels in order of precendence - LEVELS = [:trace, :debug, :info, :warn, :error] + # Logging levels in order of precedence + LEVELS = [:trace, :debug, :info, :warn, :error, :fatal] # Mapping of Rails and Ruby Logger levels to SemanticLogger levels MAP_LEVELS = [] ::Logger::Severity.constants.each do |constant| MAP_LEVELS[::Logger::Severity.const_get(constant)] = LEVELS.find_index(constant.downcase.to_sym) || LEVELS.find_index(:error) @@ -47,22 +47,22 @@ def self.default_level @@default_level end - attr_reader :application, :level + attr_reader :name, :level @@default_level = :info # Create a Logger instance # Parameters: # application: A class, module or a string with the application/class name # to be used in the logger # options: # :level The initial log level to start with for this logger instance - def initialize(application, options={}) - @application = application.is_a?(String) ? application : application.name + def initialize(klass, options={}) + @name = klass.is_a?(String) ? klass : klass.name set_level(options[:level] || self.class.default_level) end # Set the logging level # Must be one of the values in #LEVELS @@ -75,50 +75,125 @@ # # Implement the log level query # logger.debug? # # Example: - # logger = SemanticLogging::Logger.new('MyApplication') + # logger = SemanticLogging::Logger.new(self) # logger.debug("Only display this if log level is set to Debug or lower") # # # Log semantic information along with a text message # logger.info("Request received", :user => "joe", :duration => 100) # # # Log an exception in a semantic way # logger.info("Parsing received XML", exc) # LEVELS.each_with_index do |level, index| class_eval <<-EOT, __FILE__, __LINE__ - def #{level}(message = nil, data = nil, &block) # def trace(message = nil, data = nil, &block) - if @level_index <= #{index} # if @level_index <= 0 - self.class.appenders.each {|appender| appender.log(:#{level}, application, message, data, &block) } # self.class.appenders.each {|appender| appender.log(:trace, application, message, data, &block) } - true # true - else # else - false # false - end # end - end # end + def #{level}(message = nil, payload = nil) + if @level_index <= #{index} + if block_given? && (result = yield) + if result.is_a?(String) + message = message.nil? ? result : "\#{message} -- \#{result.to_s}" + else + payload = payload.nil? ? sresult : payload.merge(result) + end + end + self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, Time.now) + true + else + false + end + end - def #{level}? # def trace? - @level_index <= #{index} # @level_index <= 0 - end # end + def #{level}? + @level_index <= #{index} + end + + def benchmark_#{level}(message, payload = nil) + raise "Mandatory block missing" unless block_given? + if @level_index <= #{index} + start = Time.now + result = yield + self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, start, Time.now - start) + result + else + yield + end + end EOT end - # Semantic Logging does not support :fatal or :unknown levels since these + # Semantic Logging does not support :unknown level since these # are not understood by the majority of the logging providers - # Map them to :error - alias :fatal :error - alias :fatal? :error? + # Map it to :error alias :unknown :error alias :unknown? :error? - # forward other calls to ActiveResource::BufferedLogger - # #silence is not implemented since it is not thread safe prior to Rails 3.2 - # #TODO implement a thread safe silence method + # #TODO implement a thread safe #silence method + # Returns [Integer] the number of log entries that have not been 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.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 + def flush + self.class.flush + end + + # Flush all pending log entry disk, database, etc. + # All pending log writes are completed and each appender is flushed in turn + def self.flush + return false unless @@appender_thread.alive? + + reply_queue = Queue.new + queue << { :command => :flush, :reply_queue => reply_queue } + reply_queue.pop + end + + # Internal logger for SymanticLogger + # For example when an appender is not working etc.. + # By default logs to STDERR, replace with another Ruby logger or Rails + # logger, but never to SemanticLogger itself + sync_cattr_accessor :logger do + require 'logger' + l = ::Logger.new(STDOUT) + l.level = ::Logger::INFO + l + end + + ############################################################################ protected + # 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 + + Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration) + + # For JRuby include the Thread name rather than its id + if defined? Java + def self.thread_name + Java::java.lang::Thread.current_thread.name + end + else + def self.thread_name + Thread.object_id + end + end + # Verify and set the level def set_level(level) index = if level.is_a?(Integer) MAP_LEVELS[level] elsif level.is_a?(String) @@ -130,7 +205,53 @@ raise "Invalid level:#{level.inspect} being requested. Must be one of #{LEVELS.inspect}" unless index @level_index = index @level = level 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 + begin + # #TODO Logger needs it's own "reliable" appender ;) + # For example if an appender constantly fails + # ( bad filename or path, invalid server ) + logger.debug "SemanticLogger::Logger Appender Thread Started" + while message=queue.pop + if message.is_a? Log + appenders.each {|appender| appender.log(message) } + else + case message[:command] + when :shutdown + appenders.each {|appender| appender.flush } + message[:reply_queue] << true + logger.debug "SemanticLogger::Logger appenders flushed, now shutting down" + break + when :flush + appenders.each {|appender| appender.flush } + message[:reply_queue] << true + logger.debug "SemanticLogger::Logger appenders flushed" + end + end + end + rescue Exception => exception + logger.fatal "SemanticLogger::Logger Appender Thread crashed: #{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}" + exit(-2) + 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" + reply_queue = Queue.new + queue << { :command => :shutdown, :reply_queue => reply_queue } + reply_queue.pop + end + end -end \ No newline at end of file +end