# Equivalent to a header guard in C/C++ # Used to prevent the class/module from being loaded more than once unless defined? Logging require 'thread' begin require 'fastthread'; rescue LoadError; end # TODO: Windows Log Service appender # TODO: Option to buffer log messages at the appender level # extend the concept found in the e-mail appender into the other IO # appenders # # module Logging # :stopdoc: VERSION = '0.9.6' LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR WIN32 = %r/djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM LEVELS = {} LNAMES = [] # :startdoc: class << self # call-seq: # Logging.configure( filename ) # Logging.configure { block } # # Configures the Logging framework using the configuration information # found in the given file. The file extension should be either '.yaml' # or '.yml' (XML configuration is not yet supported). # def configure( *args, &block ) if block return ::Logging::Config::Configurator.process(&block) end filename = args.shift raise ArgumentError, 'a filename was not given' if filename.nil? case File.extname(filename) when '.yaml', '.yml' ::Logging::Config::YamlConfigurator.load(filename, *args) else raise ArgumentError, 'unknown configuration file format' end end # call-seq: # Logging.logger( device, age = 7, size = 1048576 ) # Logging.logger( device, age = 'weekly' ) # # This convenience method returns a Logger instance configured to behave # similarly to a core Ruby Logger instance. # # The _device_ is the logging destination. This can be a filename # (String) or an IO object (STDERR, STDOUT, an open File, etc.). The # _age_ is the number of old log files to keep or the frequency of # rotation (+daily+, +weekly+, or +monthly+). The _size_ is the maximum # logfile size and is only used when _age_ is a number. # # Using the same _device_ twice will result in the same Logger instance # being returned. For example, if a Logger is created using STDOUT then # the same Logger instance will be returned the next time STDOUT is # used. A new Logger instance can be obtained by closing the previous # logger instance. # # log1 = Logging.logger(STDOUT) # log2 = Logging.logger(STDOUT) # log1.object_id == log2.object_id #=> true # # log1.close # log2 = Logging.logger(STDOUT) # log1.object_id == log2.object_id #=> false # # The format of the log messages can be changed using a few optional # parameters. The :pattern can be used to change the log # message format. The :date_pattern can be used to change how # timestamps are formatted. # # log = Logging.logger(STDOUT, # :pattern => "[%d] %-5l : %m\n", # :date_pattern => "%Y-%m-%d %H:%M:%S.%s") # # See the documentation for the Logging::Layouts::Pattern class for a # full description of the :pattern and :date_pattern formatting strings. # def logger( *args ) opts = args.pop if args.last.instance_of?(Hash) opts ||= Hash.new dev = args.shift keep = age = args.shift size = args.shift name = case dev when String; dev when File; dev.path else dev.object_id.to_s end repo = ::Logging::Repository.instance return repo[name] if repo.has_logger? name l_opts = { :pattern => "%.1l, [%d #%p] %#{::Logging::MAX_LEVEL_LENGTH}l : %m\n", :date_pattern => '%Y-%m-%dT%H:%M:%S.%s' } [:pattern, :date_pattern, :date_method].each do |o| l_opts[o] = opts.delete(o) if opts.has_key? o end layout = ::Logging::Layouts::Pattern.new(l_opts) a_opts = Hash.new a_opts[:size] = size if size.instance_of?(Fixnum) a_opts[:age] = age if age.instance_of?(String) a_opts[:keep] = keep if keep.instance_of?(Fixnum) a_opts[:filename] = dev if dev.instance_of?(String) a_opts[:layout] = layout a_opts.merge! opts appender = case dev when String ::Logging::Appenders::RollingFile.new(name, a_opts) else ::Logging::Appenders::IO.new(name, dev, a_opts) end logger = ::Logging::Logger.new(name) logger.add_appenders appender logger.additive = false class << logger def close @appenders.each {|a| a.close} h = ::Logging::Repository.instance.instance_variable_get :@h h.delete(@name) class << self; undef :close; end end end logger end # call-seq: # Logging.init( levels ) # # Defines the levels available to the loggers. The _levels_ is an array # of strings and symbols. Each element in the array is downcased and # converted to a symbol; these symbols are used to create the logging # methods in the loggers. # # The first element in the array is the lowest logging level. Setting the # logging level to this value will enable all log messages. The last # element in the array is the highest logging level. Setting the logging # level to this value will disable all log messages except this highest # level. # # This method should only be invoked once to configure the logging # levels. It is automatically invoked with the default logging levels # when the first logger is created. # # The levels "all" and "off" are reserved and will be ignored if passed # to this method. # # Example: # # Logging.init :debug, :info, :warn, :error, :fatal # log = Logging::Logger['my logger'] # log.level = :warn # log.warn 'Danger! Danger! Will Robinson' # log.info 'Just FYI' # => not logged # # or # # Logging.init %w(DEBUG INFO NOTICE WARNING ERR CRIT ALERT EMERG) # log = Logging::Logger['syslog'] # log.level = :notice # log.warning 'This is your first warning' # log.info 'Just FYI' # => not logged # def init( *args ) args = %w(debug info warn error fatal) if args.empty? args.flatten! levels = ::Logging::LEVELS.clear names = ::Logging::LNAMES.clear id = 0 args.each do |lvl| lvl = levelify lvl unless levels.has_key?(lvl) or lvl == 'all' or lvl == 'off' levels[lvl] = id names[id] = lvl.upcase id += 1 end end longest = names.inject {|x,y| (x.length > y.length) ? x : y} longest = 'off' if longest.length < 3 module_eval "MAX_LEVEL_LENGTH = #{longest.length}", __FILE__, __LINE__ levels.keys end # call-seq: # Logging.format_as( obj_format ) # # Defines the default _obj_format_ method to use when converting objects # into string representations for logging. _obj_format_ can be one of # :string, :inspect, or :yaml. These # formatting commands map to the following object methods # # * :string => to_s # * :inspect => inspect # * :yaml => to_yaml # # An +ArgumentError+ is raised if anything other than +:string+, # +:inspect+, +:yaml+ is passed to this method. # def format_as( f ) f = f.intern if f.instance_of? String unless [:string, :inspect, :yaml].include? f raise ArgumentError, "unknown object format '#{f}'" end module_eval "OBJ_FORMAT = :#{f}", __FILE__, __LINE__ end # call-seq: # Logging.backtrace #=> true or false # Logging.backtrace( value ) #=> true or false # # Without any arguments, returns the global exception backtrace logging # value. When set to +true+ backtraces will be written to the logs; when # set to +false+ backtraces will be suppressed. # # When an argument is given the global exception backtrace setting will # be changed. Value values are "on", :on and +true+ to # turn on backtraces and "off", :off and +false+ to # turn off backtraces. # def backtrace( b = nil ) @backtrace = true unless defined? @backtrace return @backtrace if b.nil? @backtrace = case b when :on, 'on', true; true when :off, 'off', false; false else raise ArgumentError, "backtrace must be true or false" end end # Returns the version string for the library. # def version VERSION end # Returns the library path for the module. If any arguments are given, # they will be joined to the end of the libray path using # File.join. # def libpath( *args ) args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten) end # Returns the lpath for the module. If any arguments are given, # they will be joined to the end of the path using # File.join. # def path( *args ) args.empty? ? PATH : ::File.join(PATH, args.flatten) end # Utility method used to rquire all files ending in .rb that lie in the # directory below this file that has the same name as the filename passed # in. Optionally, a specific _directory_ name can be passed in such that # the _filename_ does not have to be equivalent to the directory. # def require_all_libs_relative_to( fname, dir = nil ) dir ||= ::File.basename(fname, '.*') search_me = ::File.expand_path( ::File.join(::File.dirname(fname), dir, '*.rb')) Dir.glob(search_me).sort.each {|rb| require rb} end # call-seq: # show_configuration( io = STDOUT, logger = 'root' ) # # This method is used to show the configuration of the logging # framework. The information is written to the given _io_ stream # (defaulting to stdout). Normally the configuration is dumped starting # with the root logger, but any logger name can be given. # # Each line contains information for a single logger and it's appenders. # A child logger is indented two spaces from it's parent logger. Each # line contains the logger name, level, additivity, and trace settings. # Here is a brief example: # # root ........................... *info -T # LoggerA ...................... info +A -T # LoggerA::LoggerB ........... info +A -T # LoggerA::LoggerC ........... *debug +A -T # LoggerD ...................... *warn -A +T # # The lines can be deciphered as follows: # # 1) name - the name of the logger # # 2) level - the logger level; if it is preceeded by an # asterisk then the level was explicitly set for that # logger (as opposed to being inherited from the parent # logger) # # 3) additivity - a "+A" shows the logger is additive, and log events # will be passed up to the parent logger; "-A" shows # that the logger will *not* pass log events up to the # parent logger # # 4) trace - a "+T" shows that the logger will include trace # information in generated log events (this includes # filename and line number of the log message; "-T" # shows that the logger does not include trace # information in the log events) # # If a logger has appenders then they are listed, on per line, # immediately below the logger. Appender lines are pre-pended with a # single dash: # # root ........................... *info -T # - # LoggerA ...................... info +A -T # LoggerA::LoggerB ........... info +A -T # LoggerA::LoggerC ........... *debug +A -T # LoggerD ...................... *warn -A +T # - # # We can see in this configuration dump that all the loggers will append # to stdout via the Stdout appender configured in the root logger. All # the loggers are additive, and so their generated log events will be # passed up to the root logger. # # The exception in this configuration is LoggerD. Its additivity is set # to false. It uses its own appender to send messages to stderr. # def show_configuration( io = STDOUT, logger = 'root', indent = 0 ) logger = ::Logging::Logger[logger] unless ::Logging::Logger === logger logger._dump_configuration(io, indent) indent += 2 children = ::Logging::Repository.instance.children(logger.name) children.sort {|a,b| a.name <=> b.name}.each do |child| ::Logging.show_configuration(io, child, indent) end nil end # :stopdoc: # Convert the given level into a connaconical form - a lowercase string. def levelify( level ) case level when String; level.downcase when Symbol; level.to_s.downcase else raise ArgumentError, "levels must be a String or Symbol" end end # Convert the given level into a level number. def level_num( level ) l = levelify level case l when 'all'; 0 when 'off'; LEVELS.length else begin; Integer(l); rescue ArgumentError; LEVELS[l] end end end # Internal logging method for use by the framework. def log_internal( level = 1, &block ) ::Logging::Logger[::Logging].__send__(levelify(LNAMES[level]), &block) end # :startdoc: end end # module Logging Logging.require_all_libs_relative_to(__FILE__) Logging.require_all_libs_relative_to(__FILE__, 'logging/config') # This exit handler will close all the appenders that exist in the system. # This is needed for closing IO streams and connections to the syslog server # or e-mail servers, etc. # at_exit { Logging::Appender.instance_variable_get(:@appenders).values.each do |ap| ap.close end } end # unless defined? # EOF