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: {})
-
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)
-
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)
-
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