Module: Squash::Ruby

Defined in:
lib/squash/ruby.rb

Constant Summary collapse

EXCEPTION_RESERVED_IVARS =

Reserved instance variables that cannot be keys in a user-data hash.

%W( mesg bt )
CONFIGURATION_DEFAULTS =

Default values for different configuration variables.

{
    :notify_path                => "/api/1.0/notify",
    :deploy_path                => "/api/1.0/deploy",
    :open_timeout               => 15,
    :transmit_timeout           => 15,
    :ignored_exception_classes  => [],
    :ignored_exception_messages => {},
    :ignored_exception_procs    => [],
    :failsafe_log               => "squash.failsafe.log",
    :repository_root            => Dir.getwd,
    :project_root               => Dir.getwd
}
JSON_NATIVE_TYPES =

Types that are serialized directly to JSON, rather than to a hash of object information. Subclasses are not considered members of this array.

[String, NilClass, TrueClass, FalseClass, Integer,
Fixnum, Float]
TOP_LEVEL_USER_DATA =

Array of user-data fields that should be moved out of the user data to become top-level attributes. A Rails client library would expand this constant to include Rails-specific fields, for example.

[]

Class Method Summary collapse

Class Method Details

.add_user_data(user_data) { ... } ⇒ Object

Adds user data to any exception raised within a block of code, and re-raises the exception.

Parameters:

  • user_data (Hash)

    User data to add to an exception.

Yields:

  • The code to run.

Returns:

  • The result of the block.

Raises:

  • (ArgumentError)

    If ‘data` contains the keys `mesg` or `bt`.



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/squash/ruby.rb', line 165

def self.add_user_data(user_data)
  raise ArgumentError, "Squash::Ruby.add_user_data must be called with a block" unless block_given?
  check_user_data user_data

  begin
    yield
  rescue Object => err
    user_data.each { |ivar, val| err.send :instance_variable_set, :"@#{ivar}", val }
    raise
  end
end

.check_user_data(data) ⇒ Object

Raises:

  • (ArgumentError)


233
234
235
236
# File 'lib/squash/ruby.rb', line 233

def self.check_user_data(data)
  bad_ivars = EXCEPTION_RESERVED_IVARS.select { |name| data.keys.map { |k| k.to_s }.include? name }
  raise ArgumentError, "The following cannot be used as user-data keys: #{bad_ivars.join(', ')}" unless bad_ivars.empty?
end

.configure(options) ⇒ Object

Sets configuration options for the client from a hash. See the README for a list of configuration options. Subsequent calls will merge in new configuration options.

You must at a minimum specify the ‘:api_key` and `:environment` settings (see the README.md file).

Parameters:

  • options (Hash)

    Configuration options.



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

def self.configure(options)
  @configuration = (@configuration || CONFIGURATION_DEFAULTS.dup).merge(options.inject({}) { |hsh, (k, v)| hsh[(k.to_sym rescue k)] = v; hsh })
end

.fail_silently(exception_classes = nil, options = {}) { ... } ⇒ Object

Executes the block, suppressing and silently reporting any exceptions to Squash. This allows you to ensure that a non-critical block of code does not halt your application while still receiving exception notifications in Squash.

Parameters:

  • exception_classes (Array<Class>) (defaults to: nil)

    A list of exception classes to report silently. Exceptions not of these classes (or their subclasses) will raise (and presumably be handled by Squash elsewhere in your code).

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

    Additional options to pass to notify.

Yields:

  • The code to suppress exceptions in.

Returns:

  • The result of the block.

Raises:

  • (ArgumentError)

    If no block is provided.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/squash/ruby.rb', line 191

def self.fail_silently(exception_classes_or_options=nil, options=nil)
  raise ArgumentError, "Squash::Ruby.exception_classes must be called with a block" unless block_given?

  exception_classes = if options
                        exception_classes_or_options
                      else
                        if exception_classes_or_options.kind_of?(Hash) then
                          options = exception_classes_or_options
                          nil
                        else
                          exception_classes_or_options
                        end
                      end
  options           ||= {}

  exception_classes = [exception_classes] if exception_classes.kind_of?(Class)

  begin
    yield
  rescue Object => err
    if exception_classes.nil? || exception_classes.detect { |e| err.kind_of?(e) }
      Squash::Ruby.notify err, options
    else
      raise
    end
  end
end

.ignore_exceptions(exception_classes = nil) { ... } ⇒ Object

Suppresses reporting of certain exceptions within a block of code. Any exceptions raised in the block will continue to be raised, however.

Let’s take a few examples. If ‘exception_classes` is `[RangeError]`, then obviously any raised `RangeError`s will not be reported. If `StandardError` is raised, it will be reported, because it’s a superclass of ‘RangeError`. If `FloatDomainError` is raised, it _will not_ be reported because it is a subclass of `RangeError`. Confusing? Sure, but I’m pretty sure this is the behavior most people would expect.

Parameters:

  • exception_classes (Array<Class>) (defaults to: nil)

    A list of exception classes to ignore. If not provided, ignores all exceptions raised in the block.

Yields:

  • The code to ignore exceptions in.

Returns:

  • The result of the block.

Raises:

  • (ArgumentError)

    If no block is provided.



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/squash/ruby.rb', line 145

def self.ignore_exceptions(exception_classes=nil)
  raise ArgumentError, "Squash::Ruby.ignore_exceptions must be called with a block" unless block_given?
  exception_classes = [exception_classes] if exception_classes.kind_of?(Class)

  begin
    yield
  rescue Object => err
    err.instance_variable_set(:@_squash_do_not_report, true) if exception_classes.nil? || exception_classes.map { |e| e.ancestors }.flatten.include?(err.class)
    raise
  end
end

.notify(exception, user_data = {}) ⇒ true, false

Notifies Squash of an exception.

Parameters:

  • exception (Object)

    The exception. Must at least duck-type an ‘Exception` subclass.

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

    Any additional context-specific information about the exception.

Returns:

  • (true, false)

    Whether the exception was reported to Squash. (Some exceptions are ignored and not reported to Squash.)

Raises:

  • (StandardError)

    If Squash has not yet been fully configured (see configure).



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/squash/ruby.rb', line 65

def self.notify(exception, user_data={})
  unless configuration(:disabled)
    raise "The :api_key configuration is required" unless configuration(:api_key)
    raise "The :api_host configuration is required" unless configuration(:api_host)
    raise "The :environment configuration is required" unless configuration(:environment)
  end

  begin
    blob = self.generate_exception(exception, user_data)
    return false if blob.nil?

    self.transmit_exception(blob)
    return true
  rescue Object => nested_error
    raise if configuration(:disable_failsafe)
    failsafe_handler exception, nested_error
    :failsafe # a perfect example of http://thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx
  end
end

.record(exception_class, message, user_data = {}) ⇒ Object .record(message, user_data = {}) ⇒ Object

Raises an exception and immediately catches it and sends it to Squash. The exception is then eaten. This is meant to be used as a hackneyed form of event logging. You can pass in any user data you wish to record with the event.

It should be emphasized that Squash is not a logging system, and there are far more appropriate products for this kind of thing, but this method is here nonetheless.

Overloads:

  • .record(exception_class, message, user_data = {}) ⇒ Object

    Specify both the exception class and the message.

    Parameters:

    • exception_class (Class)

      The exception class to raise.

    • message (String)

      The exception message.

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

      Additional information to give to notify.

  • .record(message, user_data = {}) ⇒ Object

    Specify only the message. The exception class will be ‘StandardError`.

    Parameters:

    • message (String)

      The exception message.

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

      Additional information to give to notify.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/squash/ruby.rb', line 104

def self.record(exception_class_or_message, message_or_options=nil, data=nil)
  if message_or_options && data
    exception_class = exception_class_or_message
    message         = message_or_options
  elsif message_or_options.kind_of?(String)
    message         = message_or_options
    exception_class = exception_class_or_message
  elsif message_or_options.kind_of?(Hash)
    data            = message_or_options
    message         = exception_class_or_message
    exception_class = StandardError
  elsif message_or_options.nil?
    message         = exception_class_or_message
    exception_class = StandardError
  else
    raise ArgumentError
  end

  begin
    raise exception_class, message
  rescue exception_class => error
    notify error, data || {}
  end
end