module Escalate

def self.included(base)

def self.included(base)
  base.extend self
  base.escalate_logger_block = Thread.current[:escalate_logger_block] || -> { base.try(:logger) }
end

def clear_on_escalate_callbacks

def clear_on_escalate_callbacks
  on_escalate_callbacks.clear
end

def default_escalate_logger

def default_escalate_logger
  @default_escalate_logger ||= Logger.new(STDERR)
end

def ensure_failsafe(message)

def ensure_failsafe(message)
  yield
rescue Exception => ex
  STDERR.puts("[Escalator] #{message}: #{ex.class.name}: #{ex.message}")
end

def escalate(exception, location_message, logger, context: {})

Parameters:
  • context (Hash) --
  • logger (Logger) --
  • location_message (String) --
  • exception (Exception) --
def escalate(exception, location_message, logger, context: {})
  ensure_failsafe("Exception rescued while escalating #{exception.inspect}") do
    if on_escalate_callbacks.none? || on_escalate_callbacks.values.any? { |block| block.instance_variable_get(LOG_FIRST_INSTANCE_VARIABLE) }
      logger_allows_added_context?(logger) or context_string = " (#{context.inspect})"
      error_message = <<~EOS
        [Escalate] #{location_message}#{context_string}
        #{exception.class.name}: #{exception.message}
        #{exception.backtrace.join("\n")}
      EOS
      if context_string
        logger.error(error_message)
      else
        logger.error(error_message, **context)
      end
    end
    on_escalate_callbacks.values.each do |block|
      ensure_failsafe("Exception rescued while escalating #{exception.inspect} to #{block.inspect}") do
        block.call(exception, location_message, **context)
      end
    end
  end
end

def escalate(exception, location_message, context: {})

def escalate(exception, location_message, context: {})
  Escalate.escalate(exception, location_message, escalate_logger, context: context)
end

def escalate_logger

def escalate_logger
  escalate_logger_block&.call || default_escalate_logger
end

def logger_allows_added_context?(logger)

def logger_allows_added_context?(logger)
  defined?(ContextualLogger::LoggerMixin) &&
    logger.is_a?(ContextualLogger::LoggerMixin)
end

def mixin(&logger_block)

Parameters:
  • logger_block (Proc) --
def mixin(&logger_block)
  Thread.current[:escalate_logger_block] = logger_block
  Module.new do
    def self.included(base)
      base.extend self
      base.escalate_logger_block = Thread.current[:escalate_logger_block] || -> { base.try(:logger) }
    end
    attr_accessor :escalate_logger_block
    def escalate(exception, location_message, context: {})
      Escalate.escalate(exception, location_message, escalate_logger, context: context)
    end
    def rescue_and_escalate(location_message, context: {},
                            exceptions: DEFAULT_RESCUE_EXCEPTIONS,
                            pass_through_exceptions: DEFAULT_PASS_THROUGH_EXCEPTIONS,
                            &block)
      yield
    rescue *Array(pass_through_exceptions)
      raise
    rescue *Array(exceptions) => exception
      escalate(exception, location_message, context: context)
    end
    private
    def escalate_logger
      escalate_logger_block&.call || default_escalate_logger
    end
    def default_escalate_logger
      @default_escalate_logger ||= Logger.new(STDERR)
    end
  end
end

def on_escalate(log_first: true, name: nil, &block)

Parameters:
  • name: (string | Array) --
  • log_first: (boolean) -- true
def on_escalate(log_first: true, name: nil, &block)
  block.instance_variable_set(LOG_FIRST_INSTANCE_VARIABLE, log_first)
  on_escalate_callbacks[name || block.source_location] = block
end

def rescue_and_escalate(location_message, context: {},

def rescue_and_escalate(location_message, context: {},
                        exceptions: DEFAULT_RESCUE_EXCEPTIONS,
                        pass_through_exceptions: DEFAULT_PASS_THROUGH_EXCEPTIONS,
                        &block)
  yield
rescue *Array(pass_through_exceptions)
  raise
rescue *Array(exceptions) => exception
  escalate(exception, location_message, context: context)
end