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