class SemanticLogger::Base

def backtrace(thread: Thread.current,

Log a thread backtrace
def backtrace(thread: Thread.current,
              level: :warn,
              message: "Backtrace:",
              payload: nil,
              metric: nil,
              metric_amount: nil)
  log = Log.new(name, level)
  return false unless meets_log_level?(log)
  backtrace =
    if thread == Thread.current
      Utils.extract_backtrace(caller)
    else
      log.thread_name = thread.name
      log.tags        = (thread[:semantic_logger_tags] || []).clone
      log.named_tags  = (thread[:semantic_logger_named_tags] || {}).clone
      thread.backtrace
    end
  # TODO: Keep backtrace instead of transforming into a text message at this point
  # Maybe log_backtrace: true
  if backtrace
    message += "\n"
    message << backtrace.join("\n")
  end
  if log.assign(message:       message,
                backtrace:     backtrace,
                payload:       payload,
                metric:        metric,
                metric_amount: metric_amount) && !filtered?(log)
    self.log(log)
  else
    false
  end
end

def fast_tag(tag, &block)

:nodoc:
def fast_tag(tag, &block)
  SemanticLogger.fast_tag(tag, &block)
end

def filtered?(log)

Whether to log the supplied message based on the current filter if any
def filtered?(log)
  return false if @filter.nil?
  @filter.is_a?(Regexp) ? (@filter =~ log.name).nil? : @filter.call(log) != true
end

def initialize(klass, level = nil, filter = nil)

end
end
(/\AExclude/ =~ log.message).nil?
def self.call(log)
module ComplexFilter
Module: A module that implements `.call`. For example:
The Proc must return true or false
Proc: Only include log messages where the supplied Proc returns true
regular expression. All other messages will be ignored
RegExp: Only include log messages where the class name matches the supplied
filter [Regexp|Proc|Module]

log messages when other appenders could be logging :info and lower
For example if set to :warn, this appender would only log :warn and :fatal
Only allow log entries of this level or higher to be written to this appender
level [Symbol]

are being logged
Name of the class, module, or other identifier for which the log messages
klass [String]
Parameters

Initializer for Abstract Class SemanticLogger::Base
def initialize(klass, level = nil, filter = nil)
  # Support filtering all messages to this logger instance.
  unless filter.nil? || filter.is_a?(Regexp) || filter.is_a?(Proc) || filter.respond_to?(:call)
    raise ":filter must be a Regexp, Proc, or implement :call"
  end
  @filter = filter.is_a?(Regexp) ? filter.freeze : filter
  @name   = klass.is_a?(String) ? klass : klass.name
  if level.nil?
    # Allow the global default level to determine this loggers log level
    @level_index = nil
    @level       = nil
  else
    self.level = level
  end
end

def level

default log level
Returns the current log level if set, otherwise it returns the global
def level
  @level || SemanticLogger.default_level
end

def level=(level)

nil if this logger instance should use the global default level
Must be one of the values in SemanticLogger::LEVELS, or

SemanticLogger.default_level
the log level in any logging instance or the default log level
Note: This level is only for this particular instance. It does not override

Set the logging level for this logger
def level=(level)
  if level.nil?
    # Use the global default level for this logger
    @level_index = nil
    @level       = nil
  else
    @level_index = Levels.index(level)
    @level       = Levels.level(@level_index)
  end
end

def level_index

set for this instance
Returns the global default level index if the level has not been explicitly
Return the level index for fast comparisons
def level_index
  @level_index || SemanticLogger.default_level_index
end

def log(_log_)

Write log data to underlying data storage
def log(_log_)
  raise NotImplementedError, "Logging Appender must implement #log(log)"
end

def log_internal(level, index, message = nil, payload = nil, exception = nil)

Log message at the specified level
def log_internal(level, index, message = nil, payload = nil, exception = nil)
  # Handle variable number of arguments by detecting exception object and payload hash.
  if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
    exception = message
    message   = nil
  elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
    exception = payload
    payload   = nil
  elsif payload && !payload.is_a?(Hash)
    message = message.nil? ? payload : "#{message} -- #{payload}"
    payload = nil
  end
  log = Log.new(name, level, index)
  should_log =
    if exception.nil? && payload.nil? && message.is_a?(Hash)
      # All arguments as a hash in the message.
      log.assign(**log.extract_arguments(message))
    elsif exception.nil? && message && payload && payload.is_a?(Hash)
      # Message supplied along with a hash with the remaining arguments.
      log.assign(**log.extract_arguments(payload, message))
    else
      # All fields supplied directly.
      log.assign(message: message, payload: payload, exception: exception)
    end
  # Add result of block to message or payload if not nil
  if block_given?
    result = yield(log)
    case result
    when String
      log.message = log.message.nil? ? result : "#{log.message} -- #{result}"
    when Hash
      log.assign_hash(result)
    end
  end
  # Log level may change during assign due to :on_exception_level
  self.log(log) if should_log && should_log?(log)
end

def measure(level, message, params = {}, &block)

Dynamically supply the log level with every measurement call
def measure(level, message, params = {}, &block)
  index = Levels.index(level)
  if level_index <= index
    measure_internal(level, index, message, params, &block)
  elsif block
    yield(params)
  end
end

def measure_internal(level, index, message, params)

Measure the supplied block and log the message
def measure_internal(level, index, message, params)
  exception = nil
  # Single parameter is a hash
  if params.empty? && message.is_a?(Hash)
    params  = message
    message = nil
  end
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    if block_given?
      if (silence_level = params[:silence])
        # In case someone accidentally sets `silence: true` instead of `silence: :error`
        silence_level = :error if silence_level == true
        silence(silence_level) { yield(params) }
      else
        yield(params)
      end
    end
  rescue Exception => e
    exception = e
  ensure
    # Must use ensure block otherwise a `return` in the yield above will skip the log entry
    log = Log.new(name, level, index)
    exception ||= params[:exception]
    message   = params[:message] if params[:message]
    duration  =
      if block_given?
        1_000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
      else
        params[:duration] || raise("Mandatory block missing when :duration option is not supplied")
      end
    # Extract options after block completes so that block can modify any of the options
    payload = params[:payload]
    # May return false due to elastic logging
    should_log = log.assign(
      message:            message,
      payload:            payload,
      min_duration:       params[:min_duration] || 0.0,
      exception:          exception,
      metric:             params[:metric],
      metric_amount:      params[:metric_amount],
      duration:           duration,
      log_exception:      params[:log_exception] || :partial,
      on_exception_level: params[:on_exception_level]
    )
    # Log level may change during assign due to :on_exception_level
    self.log(log) if should_log && should_log?(log)
    raise exception if exception
  end
end

def measure_method(index:,

For measuring methods and logging their duration.
def measure_method(index:,
                   level:,
                   message:,
                   min_duration:,
                   metric:,
                   log_exception:,
                   on_exception_level:)
  # Ignores filter, silence, payload
  exception = nil
  start     = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    yield
  rescue Exception => e
    exception = e
  ensure
    log = Log.new(name, level, index)
    # May return false due to elastic logging
    should_log = log.assign(
      message:            message,
      min_duration:       min_duration,
      exception:          exception,
      metric:             metric,
      duration:           1_000.0 * (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start),
      log_exception:      log_exception,
      on_exception_level: on_exception_level
    )
    # Log level may change during assign due to :on_exception_level
    log(log) if should_log && should_log?(log)
    raise exception if exception
  end
end

def meets_log_level?(log)

Ensure minimum log level is met
def meets_log_level?(log)
  (level_index <= (log.level_index || 0))
end

def named_tags

def named_tags
  SemanticLogger.named_tags
end

def pop_tags(quantity = 1)

:nodoc:
def pop_tags(quantity = 1)
  SemanticLogger.pop_tags(quantity)
end

def push_tags(*tags)

- For better performance with clean tags, use `SemanticLogger.push_tags`
to support Rails 4.
- This method is slow since it needs to flatten the tags and remove empty elements
Note:

Returns the list of tags pushed after flattening them out and removing blanks
def push_tags(*tags)
  # Need to flatten and reject empties to support calls from Rails 4
  new_tags = tags.flatten.collect(&:to_s).reject(&:empty?)
  SemanticLogger.push_tags(*new_tags)
end

def should_log?(log)

Whether this log entry meets the criteria to be logged by this appender.
def should_log?(log)
  meets_log_level?(log) && !filtered?(log)
end

def silence(new_level = :error, &block)

:nodoc:
def silence(new_level = :error, &block)
  SemanticLogger.silence(new_level, &block)
end

def tagged(*tags)

- For better performance with clean tags, see `SemanticLogger.tagged`.
`logger.tagged('first', 'more', 'other')`
to:
`logger.tagged([['first', nil], nil, ['more'], 'other'])`
However, this api will convert:
- It is recommended to keep tags as a list without any empty values, or contain any child arrays.
remove empty elements to support Rails 4.
- This method is slow when using multiple text tags since it needs to flatten the tags and
than just a string value in the logs, or centralized logging system.
- Named tags are the recommended approach since the tag consists of a name value pair this is more useful
Notes:

end
logger.debug('Hello World')
SemanticLogger.tagged(tracking_number: 12345) do
Named Tags (Hash) example:

end
logger.debug('Hello World')
SemanticLogger.tagged(12345, 'jack') do
Tagged example:

Returns result of block.

Add the tags or named tags to the list of tags to log for this thread whilst the supplied block is active.
def tagged(*tags)
  block = -> { yield(self) }
  # Allow named tags to be passed into the logger
  # Rails::Rack::Logger passes logs as an array with a single argument
  if tags.size == 1 && !tags.first.is_a?(Array)
    tag = tags[0]
    return yield if tag.nil? || tag == ""
    return tag.is_a?(Hash) ? SemanticLogger.named_tagged(tag, &block) : SemanticLogger.fast_tag(tag.to_s, &block)
  end
  # Need to flatten and reject empties to support calls from Rails 4
  new_tags = tags.flatten.collect(&:to_s).reject(&:empty?)
  SemanticLogger.tagged(*new_tags, &block)
end

def tags

:nodoc:
def tags
  SemanticLogger.tags
end