Module: Hubble

Extended by:
Hubble
Included in:
Hubble
Defined in:
lib/hubble.rb,
lib/hubble/version.rb,
lib/hubble/middleware.rb

Defined Under Namespace

Modules: Backend, Client Classes: Rescuer

Constant Summary collapse

VERSION =
"0.1.0"

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#backendObject

Load and initialize the exception reporting backend as specified by the ‘backend’ configuration option.

Raises ArgumentError for invalid backends.



223
224
225
# File 'lib/hubble.rb', line 223

def backend
  @backend ||= backend!
end

#raise_errors=(value) ⇒ Object (writeonly)

Sets the attribute raise_errors

Parameters:

  • value

    the value to set the attribute raise_errors to.



70
71
72
# File 'lib/hubble.rb', line 70

def raise_errors=(value)
  @raise_errors = value
end

Instance Method Details

#backend!Object



228
229
230
231
232
233
234
235
236
237
# File 'lib/hubble.rb', line 228

def backend!
  case backend_name
  when 'memory'
    Hubble::Backend::Memory.new
  when 'haystack'
    Hubble::Backend::Haystack.new(haystack)
  else
    raise ArgumentError, "Unknown backend: #{backend_name.inspect}"
  end
end

#backend_nameObject

The name of the backend that should be used to post exceptions to the exceptions-collection service. The fellowing backends are available:

memory - Dummy backend that simply save exceptions in memory. Typically

used in testing environments.

heroku - In-process posting for outside of vpn apps

Returns the String backend name. See also ‘Hubble.backend`.



53
54
55
# File 'lib/hubble.rb', line 53

def backend_name
  config['backend']
end

#boomtown!Object

Public: Trigger an Exception

Returns nothing.



281
282
283
284
# File 'lib/hubble.rb', line 281

def boomtown!
  e = ArgumentError.new("BOOMTOWN")
  report(e)
end

#cast(data) ⇒ Object

Send the exception data to the relay service using a non-waiting cast call.

data - Hash of string key => string value pairs.

Returns nothing.



215
216
217
# File 'lib/hubble.rb', line 215

def cast(data)
  backend.report(data)
end

#configObject

Hash of configuration data from lib/hubble/config.yml.



29
30
31
# File 'lib/hubble.rb', line 29

def config
  @config ||= YAML.load_file(config_file)[environment]
end

#config_fileObject

Location of config.yml config file.



34
35
36
# File 'lib/hubble.rb', line 34

def config_file
  File.expand_path('../hubble/config.yml', __FILE__)
end

#contextObject

Stack of context information to include in the next Hubble 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.



81
82
83
# File 'lib/hubble.rb', line 81

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

#default_optionsObject



302
303
304
# File 'lib/hubble.rb', line 302

def default_options
  context[0]
end

#default_options=(hash) ⇒ Object



306
307
308
# File 'lib/hubble.rb', line 306

def default_options=(hash)
  context[0] = hash
end

#environmentObject

The current “environment”. This dictates which section will be read from the config.yml config file.



40
41
42
# File 'lib/hubble.rb', line 40

def environment
  @environment ||= ENV['HUBBLE_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
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.


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/hubble.rb', line 187

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

  res = {
    'class'      => e.class.to_s,
    'message'    => e.message,
    'backtrace'  => backtrace.join("\n"),
    'rollup'     => Digest::MD5.hexdigest("#{e.class}#{backtrace[0]}")
  }

  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

#failObject



298
299
300
# File 'lib/hubble.rb', line 298

def fail
  raise "failure failure!"
end

#haystackObject

The URL where exceptions should be posted. Each exception is converted into JSON and posted to this URL.



74
75
76
# File 'lib/hubble.rb', line 74

def haystack
  ENV['HUBBLE_ENDPOINT'] || config['haystack']
end

#hostnameObject



274
275
276
# File 'lib/hubble.rb', line 274

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 'hubble'
Hubble.install_unhandled_exception_hook!

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



251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/hubble.rb', line 251

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



266
267
268
# File 'lib/hubble.rb', line 266

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

#logger=(logger) ⇒ Object



270
271
272
# File 'lib/hubble.rb', line 270

def logger=(logger)
  @logger = logger
end

#popObject

Remove the last info hash from the context stack.



100
101
102
# File 'lib/hubble.rb', line 100

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

#push(info = {}) ⇒ Object

Add info to be sent in the next Hubble 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.



92
93
94
95
96
97
# File 'lib/hubble.rb', line 92

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

#raise_errors?Boolean

Determines whether exceptions are raised instead of being reported to the exception tracking service. This is typically enabled in development and test environments. When set true, no exception information is reported and the exception is raised instead. When false (default in production environments), the exception is reported to the exception tracking service but not raised.

Returns:

  • (Boolean)


63
64
65
66
67
68
69
# File 'lib/hubble.rb', line 63

def raise_errors?
  if @raise_errors.nil?
    config['raise_errors']
  else
    @raise_errors
  end
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
  Hubble.report(e, :user => current_user)
end

Returns nothing.



126
127
128
129
130
131
132
133
# File 'lib/hubble.rb', line 126

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



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/hubble.rb', line 135

def report!(e, other = {})
  data = squash_context(exception_info(e), other)
  cast(data)
rescue Object => i
  # don't fail for any reason
  logger.debug "Hubble: #{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.



151
152
153
# File 'lib/hubble.rb', line 151

def reports
  backend.reports
end

#reset!Object

Reset the context stack to a pristine state.



105
106
107
# File 'lib/hubble.rb', line 105

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

#serviceObject Also known as: svc

Deprecated



291
292
293
294
# File 'lib/hubble.rb', line 291

def service
  warn "Hubble.service is deprecated. #{caller[0]}"
  @service ||= BERTRPC::Service.new(config['host'], config['port'])
end

#setup(_config = {}) ⇒ Object

Reset the backend and optionally override the environment configuration.

config - The optional configuration Hash.

Returns nothing.



22
23
24
25
26
# File 'lib/hubble.rb', line 22

def setup(_config={})
  config.merge!(_config)
  @backend = nil
  @raise_errors = nil
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.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/hubble.rb', line 163

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