class TeeLogger::TeeLogger

Logger that writes to multiple outputs. Behaves just like Ruby's Logger, and like a hash of String => Logger.

A typical use might be to log to STDOUT, but also to a file:

log = TeeLogger.new(STDOUT, "filename.log")
log.level = Logger::WARN # applies to all outputs
log.level = "INFO"       # convenience shortcut

By using the instance as a hash, you can also set individual log levels for individual loggers:

log = TeeLogger.new(STDOUT, "filename.log")
log.each do |name, logger|
  if name.include?("filename.log")
    logger.level = "WARN"
  else
    logger.level = "DEBUG"
  end
end

Constants

LOG_FUNCTIONS

Define log functions as strings, for internal re-use

Public Class Methods

new(*args) click to toggle source

Start with any amount of IO objects or filenames; defaults to STDOUT

# File lib/teelogger.rb, line 116
def initialize(*args)
  # Handle default
  if args.empty?
    args = [STDOUT]
  end

  # Initialization
  @default_level = Logger::Severity::INFO
  @formatter = ::TeeLogger::Formatter.new
  @loggers = {}
  @ios = {}

  # Load built-in filters
  load_filters(*args)

  # Create logs for all arguments
  args.each do |arg|
    add_logger(arg)
  end
end

Public Instance Methods

add_logger(arg) click to toggle source

Add a logger to the current loggers.

# File lib/teelogger.rb, line 60
def add_logger(arg)
  key = nil
  logger = nil
  io = nil
  if arg.is_a? String
    # We have a filename
    key = File.basename(arg)

    # Try to create the logger.
    io = File.new(arg, File::WRONLY | File::APPEND | File::CREAT)
    logger = Logger.new(io)

    # Initialize logger
    io.write "Logging to '#{arg}' initialized with level #{string_level(@default_level)}.\n"
    logger.level = convert_level(@default_level)
  else
    # We have some other object - let's hope it's an IO object
    key = nil
    case arg
    when STDOUT
      key = 'STDOUT'
    when STDERR
      key = 'STDERR'
    else
      key = arg.to_s
    end

    # Try to create the logger.
    io = arg
    logger = Logger.new(io)

    # Initialize logger
    io.write "Logging to #{key} initialized with level #{string_level(@default_level)}.\n"
    logger.level = convert_level(@default_level)
  end

  # Set the logger formatter
  logger.formatter = @formatter

  # Extend logger instances with extra functionality
  logger.extend(::TeeLogger::LoggerExtensions)
  logger.teelogger_io = io
  logger.flush_interval = DEFAULT_FLUSH_INTERVAL

  # Flush the "Logging to..." line
  logger.flush

  if not key.nil? and not logger.nil? and not io.nil?
    @loggers[key] = logger
    @ios[key] = io
  end
end
exception(message, ex) click to toggle source

Log an exception

# File lib/teelogger.rb, line 169
def exception(message, ex)
  error("#{message} got #{ex.message}:\n#{ex.backtrace.join("\n")}")
end
formatter=(formatter) click to toggle source

Set the formatter

# File lib/teelogger.rb, line 156
def formatter=(formatter)
  # Update the default formatter
  @formatter = formatter

  # Set all loggers' formatters
  @loggers.each do |key, logger|
    logger.formatter = formatter
  end
end
level=(val) click to toggle source

Set log level; override this to also accept strings

# File lib/teelogger.rb, line 140
def level=(val)
  # Convert strings to the constant value
  val = convert_level(val)

  # Update the default log level
  @default_level = val

  # Set all loggers' log levels
  @loggers.each do |key, logger|
    logger.level = val
  end
end
method_missing(meth, *args, &block) click to toggle source
# File lib/teelogger.rb, line 228
def method_missing(meth, *args, &block)
  dispatch(meth, *args, &block)
end
respond_to_missing?(meth, include_private = false) click to toggle source

Every function this class doesn't have should be mapped to the original logger

# File lib/teelogger.rb, line 209
def respond_to_missing?(meth, include_private = false)
  if @loggers.nil? or @loggers.empty?
    raise "No loggers created, can't do anything."
  end

  meth_name = meth.to_s

  # All loggers are the same, so we need to check only one of them.
  @loggers.each do |key, logger|
    if logger.respond_to?(meth_name, include_private)
      return true
    end
    break
  end

  # If this didn't work, we're also emulating a hash
  return @loggers.respond_to?(meth_name, include_private)
end

Private Instance Methods

dispatch(meth, *args, &block) click to toggle source
# File lib/teelogger.rb, line 235
def dispatch(meth, *args, &block)
  if @loggers.nil? or @loggers.empty?
    raise "No loggers created, can't do anything."
  end

  # Try dispatching the call, with preprocessing based on whether it
  # is a log function or not.
  meth_name = meth.to_s

  ret = []
  if LOG_FUNCTIONS.include? meth_name
    ret = dispatch_log(meth_name, *args)
  else
    ret = dispatch_other(meth_name, *args, &block)
  end

  # Some double checking on the return value(s).
  if not ret.empty?
    return ret
  end

  # If the method wasn't from the loggers, we'll try to send it to the
  # hash.
  return @loggers.send(meth_name, *args, &block)
end
dispatch_log(meth_name, *args) click to toggle source
# File lib/teelogger.rb, line 262
def dispatch_log(meth_name, *args)
  # Filter all arguments
  args = apply_filters(*args)

  # Compose message
  msg = args.map do |arg|
    if arg.is_a? String
      arg
    else
      arg.inspect
    end
  end
  message = msg.join("|")

  # Try to write the message to all loggers.
  ret = []
  @loggers.each do |key, logger|
    if logger.respond_to? meth_name
      ret << logger.send(meth_name, key) do
        message
      end
    end
  end
  return ret
end
dispatch_other(meth_name, *args, &block) click to toggle source
# File lib/teelogger.rb, line 289
def dispatch_other(meth_name, *args, &block)
  ret = []
  @loggers.each do |key, logger|
    if logger.respond_to? meth_name
      ret << logger.send(meth_name, *args, &block)
    end
  end
  return ret
end