class TTY::Logger

Constants

LOG_TYPES
VERSION

Public Class Methods

config() click to toggle source

Logger configuration instance

@api public

# File lib/tty/logger.rb, line 61
def self.config
  @config ||= Config.new
end
configure() { |config| ... } click to toggle source

Global logger configuration

@api public

# File lib/tty/logger.rb, line 68
def self.configure
  yield config
end
define_level(name, log_level = nil) click to toggle source

Macro to dynamically define log types

@api private

# File lib/tty/logger.rb, line 34
    def self.define_level(name, log_level = nil)
      const_level = (LOG_TYPES[name.to_sym] || log_level)[:level]

      loc = caller_locations(0, 1)[0]
      if loc
        file, line = loc.path, loc.lineno + 7
      else
        file, line = __FILE__, __LINE__ + 3
      end
      class_eval(<<-EOL, file, line)
        def #{name}(*msg, &block)
          log(:#{const_level}, *msg, &block)
        end
      EOL
    end
new(output: nil, fields: {}) { |conf| ... } click to toggle source

Create a logger instance

@example

logger = TTY::Logger.new(output: $stdout)

@param [IO] output

the output object, can be stream

@param [Hash] fields

the data fields for each log message

@api public

# File lib/tty/logger.rb, line 91
def initialize(output: nil, fields: {})
  @fields = fields
  @config = if block_given?
              conf = Config.new
              yield(conf)
              conf
            else
              self.class.config
            end
  @level = @config.level
  @handlers = @config.handlers
  @output = output || @config.output
  @ready_handlers = []
  @data_filter = DataFilter.new(@config.filters.data,
                                mask: @config.filters.mask)

  @types = LOG_TYPES.dup
  @config.types.each do |name, log_level|
    add_type(name, log_level)
  end

  @handlers.each do |handler|
    add_handler(handler)
  end
end

Public Instance Methods

<<(*msg)
Alias for: write
add_handler(handler, **options) click to toggle source

Add handler for logging messages

@example

add_handler(:console)

@example

add_handler(:console, styles: { info: { color: :yellow } })

@example

add_handler([:console, message_format: "%-10s"])

@param [Symbol, Handlers] handler

the handler name or class

@api public

# File lib/tty/logger.rb, line 147
def add_handler(handler, **options)
  h, h_opts = *(handler.is_a?(Array) ? handler : [handler, options])
  handler_type = coerce_handler(h)
  global_opts = { output: @output, config: @config }
  opts = global_opts.merge(h_opts)
  ready_handler = handler_type.new(**opts)
  @ready_handlers << ready_handler
end
add_type(name, log_level) click to toggle source

Add new log type

@example

add_type(:thanks, {level: :info})

@api private

# File lib/tty/logger.rb, line 123
def add_type(name, log_level)
  if @types.include?(name)
    raise Error, "Already defined log type #{name.inspect}"
  end

  @types[name.to_sym] = log_level
  self.class.define_level(name, log_level)
end
coerce_handler(name) click to toggle source

Coerce handler name into object

@example

coerce_handler(:console)
# => TTY::Logger::Handlers::Console

@raise [Error] when class cannot be coerced

@return [Class]

@api private

# File lib/tty/logger.rb, line 178
def coerce_handler(name)
  case name
  when String, Symbol
    Handlers.const_get(name.capitalize)
  when Class
    name
  else
    raise_handler_error
  end
rescue NameError
  raise_handler_error
end
configure() { |config| ... } click to toggle source

Instance logger configuration

@api public

# File lib/tty/logger.rb, line 75
def configure
  yield @config
end
copy(new_fields) { |new_config| ... } click to toggle source

Copy this logger

@example

logger = TTY::Logger.new
child_logger = logger.copy(app: "myenv", env: "prod")
child_logger.info("Deploying")

@return [TTY::Logger]

a new copy of this logger

@api public

# File lib/tty/logger.rb, line 209
def copy(new_fields)
  new_config = @config.to_proc.call(Config.new)
  if block_given?
    yield(new_config)
  end
  self.class.new(fields: @fields.merge(new_fields),
                 output: @output, &new_config)
end
copy_error(error, message, backtrace = nil) click to toggle source

Create a new error instance copy

@param [Exception] error @param [String] message @param [Array,nil] backtrace

@return [Exception]

@api private

# File lib/tty/logger.rb, line 333
def copy_error(error, message, backtrace = nil)
  new_error = error.exception(message)
  new_error.set_backtrace(backtrace)
  new_error
end
filter(*objects) click to toggle source

Filter message parts for any sensitive information and replace with placeholder.

@param [Array] objects

the messages to filter

@return [Array]

the filtered message

@api private

# File lib/tty/logger.rb, line 312
def filter(*objects)
  objects.map do |obj|
    case obj
    when Exception
      backtrace = Array(obj.backtrace).map { |line| swap_filtered(line) }
      copy_error(obj, swap_filtered(obj.message), backtrace)
    else
      swap_filtered(obj.to_s)
    end
  end
end
log(current_level, *msg) click to toggle source

Log a message given the severtiy level

@example

logger.log(:info, "Deployed successfully")

@example

logger.log(:info) { "Deployed successfully" }

@api public

# File lib/tty/logger.rb, line 253
def log(current_level, *msg)
  scoped_fields = msg.last.is_a?(::Hash) ? msg.pop : {}
  fields_copy = scoped_fields.dup
  if msg.empty? && block_given?
    msg = []
    Array[yield].flatten(1).each do |el|
      el.is_a?(::Hash) ? fields_copy.merge!(el) : msg << el
    end
  end
  top_caller = caller_locations(1, 1)[0]
  loc = caller_locations(2, 1)[0] || top_caller
  label = top_caller.label
  metadata = {
    level: current_level,
    time: Time.now,
    pid: Process.pid,
    name: @types.include?(label.to_sym) ? label : current_level,
    path: loc.path,
    lineno: loc.lineno,
    method: loc.base_label
  }
  event = Event.new(filter(*msg),
                    @data_filter.filter(@fields.merge(fields_copy)),
                    metadata)

  @ready_handlers.each do |handler|
    level = handler.respond_to?(:level) ? handler.level : @config.level
    handler.(event) if log?(level, current_level)
  end
  self
end
log?(level, other_level) click to toggle source

Check current level against another

@return [Symbol]

@api public

# File lib/tty/logger.rb, line 223
def log?(level, other_level)
  compare_levels(level, other_level) != :gt
end
log_at(tmp_level, &block) click to toggle source

Change current log level for the duration of the block

@example

logger.log_at :debug do
  logger.debug("logged")
end

@param [String] tmp_level

the temporary log level

@api public

# File lib/tty/logger.rb, line 296
def log_at(tmp_level, &block)
  @ready_handlers.each do |handler|
    handler.log_at(tmp_level, &block)
  end
end
raise_handler_error() click to toggle source

Raise error when unknown handler name

@api private

# File lib/tty/logger.rb, line 194
def raise_handler_error
  raise Error, "Handler needs to be a class name or a symbol name"
end
remove_handler(handler) click to toggle source

Remove log events handler

@example

remove_handler(:console)

@api public

# File lib/tty/logger.rb, line 162
def remove_handler(handler)
  handler_type = coerce_handler(handler)
  @ready_handlers.delete_if { |_handler| _handler.is_a?(handler_type) }
end
swap_filtered(obj) click to toggle source

Swap string content for filtered content

@param [String] obj

@api private

# File lib/tty/logger.rb, line 344
def swap_filtered(obj)
  obj.dup.tap do |obj_copy|
    @config.filters.message.each do |text|
      obj_copy.gsub!(text, @config.filters.mask)
    end
  end
end
write(*msg) click to toggle source

Logs streaming output.

@example

logger << "Example output"

@api public

# File lib/tty/logger.rb, line 233
def write(*msg)
  event = Event.new(filter(*msg))

  @ready_handlers.each do |handler|
    handler.(event)
  end

  self
end
Also aliased as: <<