lib/semantic_logger/logger.rb in semantic_logger-0.5.3 vs lib/semantic_logger/logger.rb in semantic_logger-0.6.0

- old
+ new

@@ -17,55 +17,46 @@ # logger = SemanticLogger::Logger.new("my.app.class") # logger.debug("Login time", :user => 'Joe', :duration => 100, :ip_address=>'127.0.0.1') # # # Now log to the Logger above as well as MongoDB at the same time # -# db = Mongo::Connection.new['production_logging'] +# db = Mongodb::Connection.new['production_logging'] # # SemanticLogger::Logger.appenders << SemanticLogger::Appender::MongoDB.new( # :db => db, # :collection_size => 25.gigabytes # ) # ... # # This will be logged to both the Ruby Logger and MongoDB # logger.debug("Login time", :user => 'Mary', :duration => 230, :ip_address=>'192.168.0.1') # -require 'logger' module SemanticLogger - class Logger + class Logger < Base include SyncAttr - # 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) - end - # Thread safe Class Attribute accessor for appenders array sync_cattr_accessor :appenders do [] end + # Initial default Level for all new instances of SemanticLogger::Logger + @@default_level = :info + # Allow for setting the global default log level # This change only applies to _new_ loggers, existing logger levels # will not be changed in any way - def self.level=(level) - @@level = level + def self.default_level=(level) + @@default_level = level end # Returns the global default log level for new Logger instances - def self.level - @@level + def self.default_level + @@default_level end - attr_reader :name, :level + attr_reader :name - @@level = :info - # Returns a Logger instance # # Return the logger for a specific class, supports class specific log levels # logger = SemanticLogger::Logger.new(self) # OR @@ -74,148 +65,15 @@ # 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(klass, options={}) + def initialize(klass, level=self.class.default_level) @name = klass.is_a?(String) ? klass : klass.name - set_level(options[:level] || self.class.level) + self.level = level end - # Set the logging level - # Must be one of the values in #LEVELS - def level=(level) - set_level(level) - end - - # Implement the log level calls - # logger.debug(message|hash|exception, hash|exception=nil, &block) - # - # Implement the log level query - # logger.debug? - # - # Example: - # 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, 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? ? result : payload.merge(result) - end - end - # Add scoped payload - if self.payload - payload = payload.nil? ? self.payload : self.payload.merge(payload) - end - self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, Time.now, nil, tags) - true - else - false - end - end - - def #{level}? - @level_index <= #{index} - end - - # Log the duration of the supplied block - # If an exception occurs in the block the exception is logged using the - # same log level. The exception will flow through to the caller unchanged - def benchmark_#{level}(message, payload = nil) - raise "Mandatory block missing" unless block_given? - if @level_index <= #{index} - start = Time.now - begin - result = yield - end_time = Time.now - # Add scoped payload - if self.payload - payload = payload.nil? ? self.payload : self.payload.merge(payload) - end - self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, end_time, 1000.0 * (end_time - start), tags) - result - rescue Exception => exc - # TODO Need to be able to have both an exception and a Payload - self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, exc, Time.now, 1000.0 * (Time.now - start), tags) - raise exc - end - else - yield - end - end - EOT - end - - # Add the supplied tags to the list of tags to log for this thread whilst - # the supplied block is active - # Returns nil if no tags are currently set - def with_tags(*tags) - current_tags = self.tags - # Check for nil tags - if tags - Thread.current[:semantic_logger_tags] = current_tags ? current_tags + tags : tags - end - yield - ensure - Thread.current[:semantic_logger_tags] = current_tags - end - - # Returns [Array] of [String] tags currently active for this thread - # Returns nil if no tags are set - def tags - Thread.current[:semantic_logger_tags] - end - - # Thread specific context information to be logged with every log entry - # - # Add a payload to all log calls on This Thread within the supplied block - # - # logger.with_payload(:tracking_number=>12345) do - # logger.debug('Hello World') - # end - # - # If a log call already includes a pyload, this payload will be merged with - # the supplied payload, with the supplied payload taking precedence - # - # logger.with_payload(:tracking_number=>12345) do - # logger.debug('Hello World', :result => 'blah') - # end - def with_payload(payload) - current_payload = self.payload - Thread.current[:semantic_logger_payload] = current_payload ? current_payload.merge(payload) : payload - yield - ensure - Thread.current[:semantic_logger_payload] = current_payload - end - - # Returns [Hash] payload to be added to every log entry in the current scope - # on this thread. - # Returns nil if no payload is currently set - def payload - Thread.current[:semantic_logger_payload] - end - - # Semantic Logging does not support :unknown level since these - # are not understood by the majority of the logging providers - # Map it to :error - alias :unknown :error - alias :unknown? :error? - - # #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 @@ -233,98 +91,44 @@ reply_queue = Queue.new queue << { :command => :flush, :reply_queue => reply_queue } reply_queue.pop end + ############################################################################ + protected + + # 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 + end + # Internal logger for SemanticLogger # 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 + # By default logs to STDERR + # Can be replaced with another Ruby logger or Rails logger, but never to + # SemanticLogger::Logger itself # # Warning: Do not use this logger directly it is intended for internal logging # within Semantic Logger itself sync_cattr_accessor :logger do - l = ::Logger.new(STDERR) - l.level = ::Logger::INFO - l + SemanticLogger::Appender::File.new(STDERR, :warn) 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 - # Struct Log - # - # level - # Log level of the supplied log call - # :trace, :debug, :info, :warn, :error, :fatal - # - # thread_name - # Name of the thread in which the logging call was called - # - # name - # Class name supplied to the logging instance - # - # message - # Text message to be logged - # - # payload - # Optional Hash or Ruby Exception object to be logged - # - # time - # The time at which the log entry was created - # - # duration - # The time taken to complete a benchmark call - # - # tags - # Any tags active on the thread when the log call was made - # - Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags) - - # 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.current.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) - level = level.downcase.to_sym - LEVELS.index(level) - else - LEVELS.index(level) - end - - 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 @@ -361,9 +165,15 @@ result = reply_queue.pop # Undefine the class variable for the queue since in test environments # at_exit can be invoked multiple times remove_class_variable(:@@queue) result + end + + # Formatting does not occur within this thread, it is done by each appender + # in the appender thread + def default_formatter + nil end end end