class Logtail::Logger

The Logtail Logger behaves exactly like the standard Ruby `::Logger`, except that it supports a transparent API for logging structured data and events.

@example Basic logging

logger.info "Payment rejected for customer #{customer_id}"

@example Logging an event

logger.info "Payment rejected", payment_rejected: {customer_id: customer_id, amount: 100}

Public Class Methods

new(*io_devices_and_loggers) click to toggle source

Creates a new Logtail::Logger instance where the passed argument is an IO device. That is, anything that responds to `#write` and `#close`.

Note, this method does not accept the same arguments as the standard Ruby `::Logger`. The Ruby `::Logger` accepts additional options controlling file rotation if the first argument is a file name. This is a design flaw that Logtail does not assume. Logging to a file, or multiple IO devices is demonstrated in the examples below.

@example Logging to STDOUT

logger = Logtail::Logger.new(STDOUT)

@example Logging to the Logtail HTTP device

http_device = Logtail::LogDevices::HTTP.new("my-logtail-source-token")
logger = Logtail::Logger.new(http_device)

@example Logging to a file (with rotation)

file_device = Logger::LogDevice.new("path/to/file.log")
logger = Logtail::Logger.new(file_device)

@example Logging to a file and the Logtail HTTP device (multiple log devices)

http_device = Logtail::LogDevices::HTTP.new("my-logtail-source-token")
file_logger = ::Logger.new("path/to/file.log")
logger = Logtail::Logger.new(http_device, file_logger)
Calls superclass method
# File lib/logtail/logger.rb, line 149
def initialize(*io_devices_and_loggers)
  if io_devices_and_loggers.size == 0
    raise ArgumentError.new("At least one IO device or Logger must be provided when " +
      "instantiating a Logtail::Logger. Ex: Logtail::Logger.new(STDOUT).")
  end

  @extra_loggers = io_devices_and_loggers[1..-1].collect do |obj|
    if is_a_logger?(obj)
      obj
    else
      self.class.new(obj)
    end
  end

  io_device = io_devices_and_loggers[0]

  super(io_device)

  # Ensure we sync STDOUT to avoid buffering
  if io_device.respond_to?(:"sync=")
    io_device.sync = true
  end

  # Set the default formatter. The formatter cannot be set during
  # initialization, and can be changed with #formatter=.
  if io_device.is_a?(LogDevices::HTTP)
    self.formatter = PassThroughFormatter.new
  elsif Config.instance.development? || Config.instance.test?
    self.formatter = MessageOnlyFormatter.new
  else
    self.formatter = JSONFormatter.new
  end

  self.level = environment_level

  after_initialize if respond_to?(:after_initialize)

  Logtail::Config.instance.debug { "Logtail::Logger instantiated, level: #{level}, formatter: #{formatter.class}" }

  @initialized = true
end

Public Instance Methods

add(severity, message = nil, progname = nil, &block) click to toggle source

Patch to ensure that the {#level} method is used instead of `@level`. This is required because of Rails' monkey patching on Logger via `::LoggerSilence`.

Calls superclass method
# File lib/logtail/logger.rb, line 218
def add(severity, message = nil, progname = nil, &block)
  return true if @logdev.nil? || (severity || UNKNOWN) < level

  @extra_loggers.each do |logger|
    logger.add(severity, message, progname, &block)
  end

  super
end
formatter=(value) click to toggle source

Sets a new formatted on the logger.

@note The formatter cannot be changed if you are using the HTTP logger backend.

Calls superclass method
# File lib/logtail/logger.rb, line 194
def formatter=(value)
  if @initialized && @logdev && @logdev.dev.is_a?(Logtail::LogDevices::HTTP) && !value.is_a?(PassThroughFormatter)
    raise ArgumentError.new("The formatter cannot be changed when using the " +
      "Logtail::LogDevices::HTTP log device. The PassThroughFormatter must be used for proper " +
      "delivery.")
  end

  super
end
level=(value) click to toggle source
Calls superclass method
# File lib/logtail/logger.rb, line 204
def level=(value)
  if value.is_a?(Symbol)
    value = level_from_symbol(value)
  end
  super
end
with_context(context, &block) click to toggle source

@private

# File lib/logtail/logger.rb, line 212
def with_context(context, &block)
  Logtail::CurrentContext.with(context, &block)
end

Private Instance Methods

environment_level() click to toggle source
# File lib/logtail/logger.rb, line 249
def environment_level
  level = ([ENV['LOG_LEVEL'].to_s.upcase, "DEBUG"] & %w[DEBUG INFO WARN ERROR FATAL UNKNOWN]).compact.first
  self.class.const_get(level)
end
is_a_logger?(obj) click to toggle source
# File lib/logtail/logger.rb, line 266
def is_a_logger?(obj)
  obj.respond_to?(:debug) && obj.respond_to?(:info) && obj.respond_to?(:warn)
end
level_from_symbol(value) click to toggle source
# File lib/logtail/logger.rb, line 254
def level_from_symbol(value)
  case value
  when :debug; DEBUG
  when :info;  INFO
  when :warn;  WARN
  when :error; ERROR
  when :fatal; FATAL
  when :unknown; UNKNOWN
  else; raise ArgumentError.new("level #{value.inspect} is not a valid logger level")
  end
end