Module: Escalate

Defined in:
lib/escalate.rb,
lib/escalate/mixin.rb,
lib/escalate/version.rb

Defined Under Namespace

Modules: Mixin Classes: Error

Constant Summary collapse

LOG_FIRST_INSTANCE_VARIABLE =
:@_escalate_log_first
DEFAULT_RESCUE_EXCEPTIONS =
[StandardError].freeze
DEFAULT_PASS_THROUGH_EXCEPTIONS =
[SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException].freeze
VERSION =
"0.3.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.on_escalate_callbacksObject (readonly)

Returns the value of attribute on_escalate_callbacks.



20
21
22
# File 'lib/escalate.rb', line 20

def on_escalate_callbacks
  @on_escalate_callbacks
end

Class Method Details

.clear_on_escalate_callbacksObject



120
121
122
# File 'lib/escalate.rb', line 120

def clear_on_escalate_callbacks
  on_escalate_callbacks.clear
end

.escalate(exception, location_message, logger, context: {}) ⇒ Object

Logs and escalated an exception

Parameters:

  • exception (Exception)

    The exception that was rescued and needs to be escalated

  • location_message (String)

    An additional message giving an indication of where in the code this exception was rescued

  • logger (Logger)

    The logger object to use when logging the exception

  • context (Hash) (defaults to: {})

    Any additional context to be tied to the escalation



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/escalate.rb', line 35

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

.mixin(&logger_block) ⇒ Object

Returns a module to be mixed into a class or module exposing the escalate method to be used for escalating and logging exceptions.

Parameters:

  • logger_block (Proc)

    A block to be used to get the logger object that Escalate should be using



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/escalate.rb', line 67

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

.on_escalate(log_first: true, name: nil, &block) ⇒ Object

Registers an escalation callback to be executed when ‘escalate` is invoked.

Parameters:

  • log_first: (boolean) (defaults to: true)

    true whether escalate should log first before escalating, or leave the logging to the escalate block

  • name: (string | Array) (defaults to: nil)

    unique name for this callback block any previously-registered block with the same name will be discarded if not provided, name defaults to ‘block.source_location`



115
116
117
118
# File 'lib/escalate.rb', line 115

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