Module: Failbot

Extended by:
Failbot, Compat
Included in:
Failbot
Defined in:
lib/failbot.rb,
lib/failbot/compat.rb,
lib/failbot/server.rb,
lib/failbot/version.rb,
lib/failbot/haystack.rb,
lib/failbot/middleware.rb,
lib/failbot/bert_backend.rb,
lib/failbot/file_backend.rb,
lib/failbot/http_backend.rb,
lib/failbot/json_backend.rb,
lib/failbot/heroku_backend.rb,
lib/failbot/memory_backend.rb

Overview

Failbot asynchronously takes exceptions and reports them to the exception logger du jour. Keeps the main app from failing or lagging if the exception logger service is down or slow.

Defined Under Namespace

Modules: Compat, Handler Classes: BERTBackend, FileBackend, HTTPBackend, Haystack, HerokuBackend, JSONBackend, MemoryBackend, Rescuer, Server

Constant Summary collapse

VERSION =
"0.9.5"

Instance Attribute Summary

Attributes included from Compat

#backend

Instance Method Summary collapse

Methods included from Compat

backend!, backend_name, cast, config, config_file, default_options, default_options=, environment, fail, haystack, raise_errors=, raise_errors?, service, setup_deprecated

Instance Method Details

#contextObject

Stack of context information to include in the next failbot report. These hashes are condensed down into one and included in the next report. Don’t mess with this structure directly - use the #push and #pop methods.



71
72
73
# File 'lib/failbot.rb', line 71

def context
  @context ||= [{'server' => hostname}]
end

#exception_info(e) ⇒ Object

Extract exception info into a simple Hash.

e - The exception object to turn into a Hash.

Returns a Hash including a ‘class’, ‘message’, ‘backtrace’, and ‘rollup’

keys. The rollup value is a MD5 hash of the exception class, file, and line
number and is used to group exceptions.


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/failbot.rb', line 177

def exception_info(e)
  backtrace = Array(e.backtrace)[0, 500]

  res = {
    'class'      => e.class.to_s,
    'message'    => e.message,
    'backtrace'  => backtrace.join("\n"),
    'ruby'       => RUBY_DESCRIPTION,
    'rollup'     => Digest::MD5.hexdigest("#{e.class}#{backtrace[0]}"),
    'created_at' => Time.now.utc.iso8601(6)
  }

  if original = (e.respond_to?(:original_exception) && e.original_exception)
    remote_backtrace  = []
    remote_backtrace << original.message
    if original.backtrace
      remote_backtrace.concat(Array(original.backtrace)[0,500])
    end
    res['remote_backtrace'] = remote_backtrace.join("\n")
  end

  res
end

#hostnameObject



236
237
238
# File 'lib/failbot.rb', line 236

def hostname
  @hostname ||= Socket.gethostname
end

#install_unhandled_exception_hook!Object

Installs an at_exit hook to report exceptions that raise all the way out of the stack and halt the interpreter. This is useful for catching boot time errors as well and even signal kills.

To use, call this method very early during the program’s boot to cover as much code as possible:

require 'failbot'
Failbot.install_unhandled_exception_hook!

Returns true when the hook was installed, nil when the hook had previously been installed by another component.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/failbot.rb', line 213

def install_unhandled_exception_hook!
  # only install the hook once, even when called from multiple locations
  return if @unhandled_exception_hook_installed

  # the $! is set when the interpreter is exiting due to an exception
  at_exit do
    boom = $!
    if boom && !@raise_errors && !boom.is_a?(SystemExit)
      report(boom, 'argv' => ([$0]+ARGV).join(" "), 'halting' => true)
    end
  end

  @unhandled_exception_hook_installed = true
end

#loggerObject



228
229
230
# File 'lib/failbot.rb', line 228

def logger
  @logger ||= Logger.new($stderr)
end

#logger=(logger) ⇒ Object



232
233
234
# File 'lib/failbot.rb', line 232

def logger=(logger)
  @logger = logger
end

#popObject

Remove the last info hash from the context stack.



90
91
92
# File 'lib/failbot.rb', line 90

def pop
  context.pop if context.size > 1
end

#push(info = {}) ⇒ Object

Add info to be sent in the next failbot report, should one occur.

info - Hash of name => value pairs to include in the exception report. block - When given, the info is removed from the current context after the

block is executed.

Returns the value returned by the block when given; otherwise, returns nil.



82
83
84
85
86
87
# File 'lib/failbot.rb', line 82

def push(info={})
  context.push(info)
  yield if block_given?
ensure
  pop if block_given?
end

#report(e, other = {}) ⇒ Object

Public: Sends an exception to the exception tracking service along with a hash of custom attributes to be included with the report. When the raise_errors option is set, this method raises the exception instead of reporting to the exception tracking service.

e - The Exception object. Must respond to #message and #backtrace. other - Hash of additional attributes to include with the report.

Examples

begin
  my_code
rescue => e
  Failbot.report(e, :user => current_user)
end

Returns nothing.



116
117
118
119
120
121
122
123
# File 'lib/failbot.rb', line 116

def report(e, other = {})
  if @raise_errors
    squash_context(exception_info(e), other) # surface problems squashing
    raise e
  else
    report!(e, other)
  end
end

#report!(e, other = {}) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
# File 'lib/failbot.rb', line 125

def report!(e, other = {})
  data = squash_context(exception_info(e), other)
  backend.report(data)
rescue Object => i
  # don't fail for any reason
  logger.debug "FAILBOT: #{data.inspect}" rescue nil
  logger.debug e.message rescue nil
  logger.debug e.backtrace.join("\n") rescue nil
  logger.debug i.message rescue nil
  logger.debug i.backtrace.join("\n") rescue nil
end

#reportsObject

Public: exceptions that were reported. Only available when using the memory and file backends.

Returns an Array of exceptions data Hash.



141
142
143
# File 'lib/failbot.rb', line 141

def reports
  backend.reports
end

#reset!Object

Reset the context stack to a pristine state.



95
96
97
# File 'lib/failbot.rb', line 95

def reset!
  @context = [context[0]]
end

#setup(settings = {}, default_context = {}) ⇒ Object

Public: Setup the backend for reporting exceptions.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/failbot.rb', line 28

def setup(settings={}, default_context={})
  deprecated_settings = %w[
    backend host port haystack workers service_logs
    raise_errors
  ]

  if settings.empty? ||
    settings.keys.any? { |key| deprecated_settings.include?(key) }
    warn "%s Deprecated Failbot.setup usage. See %s for details." % [
      caller[0], "https://github.com/github/failbot"
    ]
    return setup_deprecated(settings)
  end

  if default_context.respond_to?(:to_hash) && !default_context.to_hash.empty?
    context[0] = default_context.to_hash
  end

  self.backend =
    case (name = settings["FAILBOT_BACKEND"])
    when "bert"
      Failbot::BERTBackend.new(URI(settings["FAILBOT_BACKEND_BERT_URL"]))
    when "memory"
      Failbot::MemoryBackend.new
    when "file"
      Failbot::FileBackend.new(settings["FAILBOT_BACKEND_FILE_PATH"])
    when "http"
      Failbot::HTTPBackend.new(URI(settings["FAILBOT_HAYSTACK_URL"]))
    when 'json'
      Failbot::JSONBackend.new(settings["FAILBOT_BACKEND_JSON_HOST"], settings["FAILBOT_BACKEND_JSON_PORT"])
    else
      raise ArgumentError, "Unknown backend: #{name.inspect}"
    end

  @raise_errors = !settings["FAILBOT_RAISE"].to_s.empty?
end

#squash_context(*other) ⇒ Object

Combines all context hashes into a single hash converting non-standard data types in values to strings, then combines the result with a custom info hash provided in the other argument.

other - Optional array of hashes to also squash in on top of the context

stack hashes.

Returns a Hash with all keys and values.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/failbot.rb', line 153

def squash_context(*other)
  merged = {}
  (context + other).each do |hash|
    hash.each do |key, value|
      value = (value.call rescue nil) if value.kind_of?(Proc)
      merged[key.to_s] =
        case value
        when String, Numeric, true, false
          value.to_s
        else
          value.inspect
        end
    end
  end
  merged
end