module Logging # The +Logger+ class is the primary interface to the +Logging+ framework. # It provides the logging methods that will be called from user methods, # and it generates logging events that are sent to the appenders (the # appenders take care of sending the log events to the logging # destinations -- files, sockets, etc). # # +Logger+ instances are obtained from the +Repository+ and should # not be directly created by users. # # Example: # # log = Logging.logger['my logger'] # log.add_appenders( Logging.appenders.stdout ) # append to STDOUT # log.level = :info # log 'info' and above # # log.info 'starting foo operation' # ... # log.info 'finishing foo operation' # ... # log.fatal 'unknown exception', exception # class Logger # Returns the root logger. def self.root ::Logging::Repository.instance[:root] end class << self alias_method :instantiate, :new # `instantiate` becomes the "real" `new` end # Overrides the new method such that only one Logger will be created # for any given logger name. def self.new( *args ) args.empty? ? super : self[args.shift] end # Returns a logger instance for the given name. def self.[]( name ) repo = ::Logging::Repository.instance name = repo.to_key(name) logger = repo[name] return logger unless logger.nil? # Share the same mutex that's used by 'define_log_methods' because # it iterates over the hash of loggers, and adding a new key to a hash # while iterating over it produces an error. ::Logging::Logger.mutex.synchronize do logger = repo[name] return logger unless logger.nil? # thread-safe double checking logger = instantiate(name) repo[name] = logger repo.children(name).each { |child| child.__send__(:parent=, logger) } logger end end # This is where the actual logging methods are defined. Two methods # are created for each log level. The first is a query method used to # determine if that perticular logging level is enabled. The second is # the actual logging method that accepts a list of objects to be # logged or a block. If a block is given, then the object returned # from the block will be logged. # # Example # # log = Logging::Logger['my logger'] # log.level = :warn # # log.info? # => false # log.warn? # => true # log.warn 'this is your last warning' # log.fatal 'I die!', exception # # log.debug do # # expensive method to construct log message # msg # end # def self.define_log_methods( logger ) code = log_methods_for_level(logger.level) logger._meta_eval(code, __FILE__, __LINE__) logger end # This generator is used to define the log methods for the given `level`. # This code is evaluated in the context of a Logger instance. # # Returns log methods as a String def self.log_methods_for_level( level ) code = [] ::Logging::LEVELS.each do |name,num| code << <<-CODE undef :#{name} if method_defined? :#{name} undef :#{name}? if method_defined? :#{name}? CODE if level > num code << <<-CODE def #{name}?( ) false end def #{name}( data = nil ) false end CODE else code << <<-CODE def #{name}?( ) true end def #{name}( data = nil ) data = yield if block_given? log_event(::Logging::LogEvent.new(@name, #{num}, data, @caller_tracing)) true end CODE end end code.join("\n") end @mutex = ReentrantMutex.new # Returns a global ReentrantMutex for use when creating Logger instances # and/or updating log levels. def self.mutex @mutex end attr_reader :name, :parent, :additive, :caller_tracing # call-seq: # Logger.new( name ) # Logger[name] # # Returns the logger identified by _name_. # # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to # retrieve the logger. When _name_ is a +Class+ the class name will be # used to retrieve the logger. When _name_ is an object the name of the # object's class will be used to retrieve the logger. # # Example: # # obj = MyClass.new # # log1 = Logger.new(obj) # log2 = Logger.new(MyClass) # log3 = Logger['MyClass'] # # log1.object_id == log2.object_id # => true # log2.object_id == log3.object_id # => true # def initialize( name ) case name when String raise(ArgumentError, "logger must have a name") if name.empty? else raise(ArgumentError, "logger name must be a String") end repo = ::Logging::Repository.instance _setup(name, :parent => repo.parent(name)) end # call-seq: # log <=> other # # Compares this logger by name to another logger. The normal return codes # for +String+ objects apply. # def <=>( other ) case other when self; 0 when ::Logging::RootLogger; 1 when ::Logging::Logger; @name <=> other.name else raise ArgumentError, 'expecting a Logger instance' end end # call-seq: # log << "message" # # Log the given message without any formatting and without performing any # level checks. The message is logged to all appenders. The message is # passed up the logger tree if this logger's additivity is +true+. # def <<( msg ) @appenders.each {|a| a << msg} @parent << msg if @additive end alias_method :write, :<< # call-seq: # add( severity, message = nil ) {block} # # Log a message if the given severity is high enough. This is the generic # logging method. Users will be more inclined to use #debug, #info, #warn, # #error, and #fatal. # # Message format: +message+ can be any object, but it has to be # converted to a String in order to log it. The Logging::format_as # method is used to determine how objects chould be converted to # strings. Generally, +inspect+ is used. # # A special case is an +Exception+ object, which will be printed in # detail, including message, class, and backtrace. # # If a _message_ is not given, then the return value from the block is # used as the message to log. This is useful when creating the actual # message is an expensive operation. This allows the logger to check the # severity against the configured level before actually constructing the # message. # # This method returns +true+ if the message was logged, and +false+ is # returned if the message was not logged. # def add( lvl, data = nil, progname = nil ) lvl = Integer(lvl) return false if lvl < level if data.nil? if block_given? data = yield else data = progname end end log_event(::Logging::LogEvent.new(@name, lvl, data, @caller_tracing)) true end # call-seq: # additive = true # # Sets the additivity of the logger. Acceptable values are +true+, # 'true', +false+, 'false', or +nil+. In this case +nil+ does not # change the additivity # def additive=( val ) @additive = case val when true, 'true'; true when false, 'false'; false when nil; @additive else raise ArgumentError, 'expecting a boolean' end end # call-seq: # caller_tracing = true # # Sets the caller tracing of the logger. Acceptable values are +true+, # 'true', +false+, 'false', or +nil+. In this case +nil+ does not change # the tracing. # def caller_tracing=( val ) @caller_tracing = case val when true, 'true'; true when false, 'false'; false when nil; @caller_tracing else raise ArgumentError, 'expecting a boolean' end end # call-seq: # level => integer # # Returns an integer which is the defined log level for this logger. # def level return @level unless @level.nil? @parent.level end # call-seq: # level = :all # # Set the level for this logger. The level can be either a +String+, a # +Symbol+, or an +Integer+. An +ArgumentError+ is raised if this is not # the case. # # There are two special levels -- "all" and "off". The former will # enable log messages from this logger. The latter will disable all log # messages from this logger. # # Setting the logger level to +nil+ will cause the parent's logger level # to be used. # # Example: # # log.level = :debug # log.level = "INFO" # log.level = 4 # log.level = 'off' # log.level = :all # # These produce an +ArgumentError+ # # log.level = Object # log.level = -1 # log.level = 1_000_000_000_000 # def level=( level ) @level = if level.nil? then level else lvl = case level when String, Symbol; ::Logging::level_num(level) when Integer; level else raise ArgumentError, "level must be a String, Symbol, or Integer" end if lvl.nil? or lvl < 0 or lvl > ::Logging::LEVELS.length raise ArgumentError, "unknown level was given '#{level}'" end lvl end define_log_methods(true) self.level end # Returns `true` if the logger has its own level defined. def has_own_level? !@level.nil? end # Returns the list of appenders. # def appenders @appenders.dup end # call-seq: # appenders = app # # Clears the current list of appenders and replaces them with _app_, # where _app_ can be either a single appender or an array of appenders. # def appenders=( args ) @appenders.clear add_appenders(*args) unless args.nil? end # call-seq: # add_appenders( appenders ) # # Add the given _appenders_ to the list of appenders, where _appenders_ # can be either a single appender or an array of appenders. # def add_appenders( *args ) args.flatten.each do |arg| o = arg.kind_of?(::Logging::Appender) ? arg : ::Logging::Appenders[arg.to_s] raise ArgumentError, "unknown appender #{arg.inspect}" if o.nil? @appenders << o unless @appenders.include?(o) end self end # call-seq: # remove_appenders( appenders ) # # Remove the given _appenders_ from the list of appenders. The appenders # to remove can be identified either by name using a +String+ or by # passing the appender instance. _appenders_ can be a single appender or # an array of appenders. # def remove_appenders( *args ) args.flatten.each do |arg| @appenders.delete_if do |a| case arg when String; arg == a.name when ::Logging::Appender; arg.object_id == a.object_id else raise ArgumentError, "#{arg.inspect} is not a 'Logging::Appender'" end end end self end # call-seq: # clear_appenders # # Remove all appenders from this logger. # def clear_appenders( ) @appenders.clear end protected # call-seq: # parent = ParentLogger # # Set the parent logger for this logger. This method will be invoked by # the +Repository+ class when a parent or child is added to the # hierarchy. # def parent=( parent ) @parent = parent end # call-seq: # log_event( event ) # # Send the given _event_ to the appenders for logging, and pass the # _event_ up to the parent if additive mode is enabled. The log level has # already been checked before this method is called. # def log_event( event ) @appenders.each {|a| a.append(event)} @parent.log_event(event) if @additive end # call-seq: # define_log_methods( force = false ) # # Define the logging methods for this logger based on the configured log # level. If the level is nil, then we will ask our parent for it's level # and define log levels accordingly. The force flag will skip this # check. # # Recursively call this method on all our children loggers. # def define_log_methods( force = false, code = nil ) return if has_own_level? and !force ::Logging::Logger.mutex.synchronize do ::Logging::Logger.define_log_methods(self) ::Logging::Repository.instance.children(name).each do |child| child.define_log_methods end end self end # :stopdoc: public # call-seq: # _meta_eval( code ) # # Evaluates the given string of _code_ if the singleton class of this # Logger object. # def _meta_eval( code, file = nil, line = nil ) meta = class << self; self end meta.class_eval code, file, line end # call-seq: # _setup( name, opts = {} ) # # Configures internal variables for the logger. This method can be used # to avoid storing the logger in the repository. # def _setup( name, opts = {} ) @name = name @parent = opts.fetch(:parent, nil) @appenders = opts.fetch(:appenders, []) @additive = opts.fetch(:additive, true) @level = opts.fetch(:level, nil) @caller_tracing = opts.fetch(:caller_tracing, false) ::Logging::Logger.define_log_methods(self) end # call-seq: # _dump_configuration( io = STDOUT, indent = 0 ) # # An internal method that is used to dump this logger's configuration to # the given _io_ stream. The configuration includes the logger's name, # level, additivity, and caller_tracing settings. The configured appenders # are also printed to the _io_ stream. # def _dump_configuration( indent = 0 ) str, spacer, base = '', ' ', 50 indent_str = indent == 0 ? '' : ' ' * indent str << indent_str str << self.name.shrink(base - indent) if (str.length + spacer.length) < base str << spacer str << '.' * (base - str.length) end str = str.ljust(base) str << spacer level_str = @level.nil? ? '' : '*' level_str << if level < ::Logging::LEVELS.length ::Logging.levelify(::Logging::LNAMES[level]) else 'off' end level_len = ::Logging::MAX_LEVEL_LENGTH + 1 str << sprintf("%#{level_len}s" % level_str) str << spacer if self.respond_to?(:additive) str << (additive ? '+A' : '-A') else str << ' ' end str << spacer str << (caller_tracing ? '+T' : '-T') str << "\n" @appenders.each do |appender| str << indent_str str << '- ' str << appender.to_s str << "\n" end return str end # :startdoc: end end