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,
    timeout_protection:         proc { |timeout, &block|
                                     timeout_protection(timeout, &block)
                                   },
}
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`.



181
182
183
184
185
186
187
188
189
190
191
# File 'lib/squash/ruby.rb', line 181

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)


253
254
255
256
# File 'lib/squash/ruby.rb', line 253

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.



248
249
250
# File 'lib/squash/ruby.rb', line 248

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.

The behavior of this method when Squash is disabled is dependent on the ‘exception_behavior_when_disabled` configuration option.

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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/squash/ruby.rb', line 211

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.



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/squash/ruby.rb', line 161

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. The behavior of this method when Squash is disabled is dependent on the ‘exception_behavior_when_disabled` configuration option.

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).



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
# File 'lib/squash/ruby.rb', line 70

def self.notify(exception, user_data={})
  if configuration(:disabled)
    case configuration(:exception_behavior_when_disabled)
      when 'log', :log
        failsafe_log '[Squash::Ruby.notify]', "Exception raised: #{exception.to_s}\n" +
                                                exception.backtrace.map { |l| "  #{l}" }.join("\n")
      when 'raise', :raise
        raise exception
      else
        # ignore
    end
    return false
  else
    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.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/squash/ruby.rb', line 120

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