lib/polytrix/logger.rb in polytrix-0.1.0.pre vs lib/polytrix/logger.rb in polytrix-0.1.0

- old
+ new

@@ -1,17 +1,374 @@ +require 'fileutils' require 'logger' module Polytrix - module ClassMethods - def logger - @logger ||= Polytrix.configuration.logger + class Logger + include ::Logger::Severity + + # @return [IO] the log device + attr_reader :logdev + + # Constructs a new logger. + # + # @param options [Hash] configuration for a new logger + # @option options [Symbol] :color color to use when when outputting + # messages + # @option options [Integer] :level the logging severity threshold + # (default: `Polytrix::DEFAULT_LOG_LEVEL`) + # @option options [String,IO] :logdev filepath String or IO object to be + # used for logging (default: `nil`) + # @option options [String] :progname program name to include in log + # messages (default: `"Polytrix"`) + # @option options [IO] :stdout a standard out IO object to use + # (default: `$stdout`) + def initialize(options = {}) + color = options[:color] + + @loggers = [] + @loggers << @logdev = logdev_logger(options[:logdev]) if options[:logdev] + @loggers << stdout_logger(options[:stdout], color) if options[:stdout] + @loggers << stdout_logger($stdout, color) if @loggers.empty? + + self.progname = options[:progname] || 'Polytrix' + self.level = options[:level] || default_log_level end - end - module Logger - include ClassMethods + def self.new_logger(implementor) # (test, implementor, index) + name = implementor.name # instance_name(test, implementor) + index = Polytrix.implementors.index(implementor) || 0 + Logger.new( + stdout: STDOUT, + color: Color::COLORS[index % Color::COLORS.size].to_sym, + logdev: File.join(Polytrix.configuration.log_root, "#{name}.log"), + level: Polytrix::Util.to_logger_level(Polytrix.configuration.log_level), + progname: name + ) + end - def self.included(base) - base.extend(ClassMethods) + class << self + private + + # @api private + # @!macro delegate_to_first_logger + # @method $1() + def delegate_to_first_logger(meth) + define_method(meth) { |*args| @loggers.first.public_send(meth, *args) } + end + + # @api private + # @!macro delegate_to_all_loggers + # @method $1() + def delegate_to_all_loggers(meth) + define_method(meth) do |*args| + result = nil + @loggers.each { |l| result = l.public_send(meth, *args) } + result + end + end + end + + # @return [Integer] the logging severity threshold + # @see http://is.gd/Okuy5p + delegate_to_first_logger :level + + # Sets the logging severity threshold. + # + # @param level [Integer] the logging severity threshold + # @see http://is.gd/H1VBFH + delegate_to_all_loggers :level= + + # @return [String] program name to include in log messages + # @see http://is.gd/5uHGK0 + delegate_to_first_logger :progname + + # Sets the program name to include in log messages. + # + # @param progname [String] the program name to include in log messages + # @see http://is.gd/f2U5Xj + delegate_to_all_loggers :progname= + + # @return [String] the date format being used + # @see http://is.gd/btmFWJ + delegate_to_first_logger :datetime_format + + # Sets the date format being used. + # + # @param format [String] the date format + # @see http://is.gd/M36ml8 + delegate_to_all_loggers :datetime_format= + + # Log a message if the given severity is high enough. + # + # @see http://is.gd/5opBW0 + delegate_to_all_loggers :add + + # Dump one or more messages to info. + # + # @param message [#to_s] the message to log + # @see http://is.gd/BCp5KV + delegate_to_all_loggers :<< + + # Log a message with severity of banner (high level). + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/pYUCYU + delegate_to_all_loggers :banner + + # Log a message with severity of debug. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/Re97Zp + delegate_to_all_loggers :debug + + # @return [true,false] whether or not the current severity level + # allows for the printing of debug messages + # @see http://is.gd/Iq08xB + delegate_to_first_logger :debug? + + # Log a message with severity of info. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/pYUCYU + delegate_to_all_loggers :info + + # @return [true,false] whether or not the current severity level + # allows for the printing of info messages + # @see http://is.gd/lBtJkT + delegate_to_first_logger :info? + + # Log a message with severity of error. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/mLwYMl + delegate_to_all_loggers :error + + # @return [true,false] whether or not the current severity level + # allows for the printing of error messages + # @see http://is.gd/QY19JL + delegate_to_first_logger :error? + + # Log a message with severity of warn. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/PX9AIS + delegate_to_all_loggers :warn + + # @return [true,false] whether or not the current severity level + # allows for the printing of warn messages + # @see http://is.gd/Gdr4lD + delegate_to_first_logger :warn? + + # Log a message with severity of fatal. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/5ElFPK + delegate_to_all_loggers :fatal + + # @return [true,false] whether or not the current severity level + # allows for the printing of fatal messages + # @see http://is.gd/7PgwRl + delegate_to_first_logger :fatal? + + # Log a message with severity of unknown. + # + # @param message_or_progname [#to_s] the message to log. In the block + # form, this is the progname to use in the log message. + # @yield evaluates to the message to log. This is not evaluated unless the + # logger's level is sufficient to log the message. This allows you to + # create potentially expensive logging messages that are only called when + # the logger is configured to show them. + # @return [nil,true] when the given severity is not high enough (for this + # particular logger), log no message, and return true + # @see http://is.gd/Y4hqpf + delegate_to_all_loggers :unknown + + # Close the logging devices. + # + # @see http://is.gd/b13cVn + delegate_to_all_loggers :close + + private + + # @return [Integer] the default logger level + # @api private + def default_log_level + Polytrix::Util.to_logger_level(Polytrix.configuration.log_level) + end + + # Construct a new standard out logger. + # + # @param stdout [IO] the IO object that represents stdout (or similar) + # @param color [Symbol] color to use when outputing messages + # @return [StdoutLogger] a new logger + # @api private + def stdout_logger(stdout, color) + logger = StdoutLogger.new(stdout) + if Polytrix.tty? + logger.formatter = proc do |_severity, _datetime, _progname, msg| + Color.colorize("#{msg}", color).concat("\n") + end + else + logger.formatter = proc do |_severity, _datetime, _progname, msg| + msg.concat("\n") + end + end + logger + end + + # Construct a new logdev logger. + # + # @param filepath_or_logdev [String,IO] a filepath String or IO object + # @return [LogdevLogger] a new logger + # @api private + def logdev_logger(filepath_or_logdev) + LogdevLogger.new(resolve_logdev(filepath_or_logdev)) + end + + # Return an IO object from a filepath String or the IO object itself. + # + # @param filepath_or_logdev [String,IO] a filepath String or IO object + # @return [IO] an IO object + # @api private + def resolve_logdev(filepath_or_logdev) + if filepath_or_logdev.is_a? String + FileUtils.mkdir_p(File.dirname(filepath_or_logdev)) + file = File.open(File.expand_path(filepath_or_logdev), 'ab') + file.sync = true + file + else + filepath_or_logdev + end + end + + # Internal class which adds a #banner method call that displays the + # message with a callout arrow. + class LogdevLogger < ::Logger + alias_method :super_info, :info + + # Dump one or more messages to info. + # + # @param msg [String] a message + def <<(msg) + @buffer ||= '' + lines, _, remainder = msg.rpartition("\n") + if lines.empty? + @buffer << remainder + else + lines.insert(0, @buffer) + lines.split("\n").each { |l| format_line(l.chomp) } + @buffer = '' + end + end + + # Log a banner message. + # + # @param msg [String] a message + def banner(msg = nil, &block) + super_info("-----> #{msg}", &block) + end + + private + + # Reformat a line if it already contains log formatting. + # + # @param line [String] a message line + # @api private + def format_line(line) + case line + when /^-----> / then banner(line.gsub(/^[ >-]{6} /, '')) + when /^>>>>>> / then error(line.gsub(/^[ >-]{6} /, '')) + when /^ / then info(line.gsub(/^[ >-]{6} /, '')) + else info(line) + end + end + end + + # Internal class which reformats logging methods for display as console + # output. + class StdoutLogger < LogdevLogger + # Log a debug message + # + # @param msg [String] a message + def debug(msg = nil, &block) + super("D #{msg}", &block) + end + + # Log an info message + # + # @param msg [String] a message + def info(msg = nil, &block) + super(" #{msg}", &block) + end + + # Log a warn message + # + # @param msg [String] a message + def warn(msg = nil, &block) + super("$$$$$$ #{msg}", &block) + end + + # Log an error message + # + # @param msg [String] a message + def error(msg = nil, &block) + super(">>>>>> #{msg}", &block) + end + + # Log a fatal message + # + # @param msg [String] a message + def fatal(msg = nil, &block) + super("!!!!!! #{msg}", &block) + end + + # Log an unknown message + # + # @param msg [String] a message + def unknown(msg = nil, &block) + super("?????? #{msg}", &block) + end end end end