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.

Yields:

  • The code to run.

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



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.

Yields:

  • The code to suppress exceptions in.

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.

Yields:

  • The code to ignore exceptions in.

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.

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.

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

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



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