Enhanced exceptions with a details hash and more
Add this line to your application's Gemfile:
gem 'yeti_exception'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install yeti_exception
YetiException::Error
is a subclass of StandardError
that adds some extra
attributes:
-
The class in which the exception was raised
-
A
Hash
of arbitrary details about the exception -
A
transient
boolean flag -
An HTTP response status
The exception's #message
is constructed as a string of key=value
pairs from
the details hash.
ex = YetiException::Error.new(self, # class
{ some: 'details', more: 'stuff' }, # details hash
false, # transient
400) # HTTP status
ex.message #=> "some=\"details\" more=\"stuff\""
Default values for transient
and http_status
will be used if not provided.
ex = YetiException::Error.new(self, { some: 'details', more: 'stuff' })
ex.transient #=> true
ex.http_status #=> 500
YetiException::Error
has some simple included subclasses for convenience.
YetiException::FinalError
is a non-transient error. Its default HTTP status
is 500.
ex = YetiException::FinalError.new(self, { some: 'details', more: 'stuff' }, 503)
ex.transient #=> false
ex = YetiException::FinalError.new(self, { some: 'details', more: 'stuff' })
ex.http_status #=> 500
YetiException::ClientError
is similar. It is intended to represent an invalid
HTTP request, so its default HTTP status is 400.
ex = YetiException::ClientError.new(self, { some: 'details', more: 'stuff' }, 404)
ex.transient #=> false
ex = YetiException::ClientError.new(self, { some: 'details', more: 'stuff' })
ex.http_status #=> 400
Custom subclasses can be useful if you want to handle specific exceptions differently.
class MySpecialError < YetiException::Error; end
begin
do_something
rescue MySpecialError => ex
puts "Special: #{ex.message}"
rescue => ex
puts "Not so special: #{ex.message}"
end
Custom subclasses can also define a static message that will be merged into the exception's details hash automatically.
class MySpecialError < YetiException::Error
def msg
'Something special'
end
end
ex = MySpecialError.new(self, { some: 'details' })
ex.details #=> {:msg=>"Something special", :some=>"details"}
The static message will not overwrite a :msg
key included explicitly in the details.
class MySpecialError < YetiException::Error
def msg
'Something special'
end
end
ex = MySpecialError.new(self, { some: 'details', msg: 'Even more special' })
ex.details #=> {:msg=>"Even more special", :some=>"details"}
YetiException::Helpers
is a mixin that provides a raise_exception
convenience method, defined as both a class and instance method.
YetiException::Error
has a klass
attribute containing the class in which the
exception was raised so that it may be used in log searching. While the raising
class can be inferred from the exception's backtrace, the class name is more
intuitive. raise_exception
removes the need to reference the class
explicitly.
class MyClass
include YetiException::Helpers
def call
raise_exception(YetiException::Error, { some: 'details' })
end
end
ex = begin
MyClass.new.call
rescue => exception
exception
end
ex.klass #=> MyClass
Any YetiException::Error
subclass can be raised, and additional parameters are
passed to the exception's #initialize
method:
raise_exception(MyCustomError, # exception class
{ some: 'details' }, # details
false, # transient
400) # HTTP status
Integration with YetiLogger
YetiException
was designed with YetiLogger
in mind. YetiLogger
automatically handles logging a details hash with an optional exception.
begin
do_something
rescue YetiException::Error => ex
log_error(ex.details, ex)
end
The class in which the exception was raised can also be included.
begin
do_something
rescue YetiException::Error => ex
log_error(ex.details.merge(klass: ex.klass), ex)
end
The http_status
attribute of YetiException::Error
can be used when rescuing
an exception in a Rails controller. If your application uses YetiException
for all errors, it makes sense to define a rescue_from
hander in
ApplicationController
. For example, for an API-only app:
class ApplicationController < ActionController::API
# Define a catch-all handler for other exceptions. Rails searches for a
# matching handler in the reverse order of their definitions.
rescue_from StandardError do |ex|
# If yeti_logger is used
log_error({
msg: 'error',
error: ex.message
}, ex)
render json: { error: ex.message }, status: 500
end
# For the YetiException exceptions with richer data, use the specified
# details and status.
rescue_from YetiException::Error do |ex|
# If yeti_logger is used
log_error(ex.details, ex)
render json: { error: ex.details }, status: ex.status
end
end
For asynchronous job frameworks like Sidekiq or
Sneakers, exceptions can be caught in the
worker entry point, and the transient
attribute can be used to retry the job
or fail it outright.
A framework-agnostic example:
class MyWorker
def entry_point
do_something
rescue YetiException::Error => ex
# If yeti_logger is used
log_error(ex.details, ex)
if ex.transient
# retry the job
else
# fail the job
end
end
end
- Fork it ( https://github.com/Yesware/yeti_exception/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request