How to serialize Exception - ruby

According to ruby-doc and apidock, you can serialize and deserialize an exception using to_json and json_create.
But after having wasted some time trying to use them, I still haven't found a way.
Calling exc.to_json gives me an empty hash, and Exception.json_create(hash) gives me this error: undefined method 'json_create' for Exception:Class
I guess I could easily recreate those functions since the source is available, but I'd rather understand what I'm doing wrong... Any idea?

The JSON module doesn't extend Exception by default. You have to require "json/add/exception". I'm not sure if this is documented anywhere:
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json
# => {"json_class":"NoMethodError","m":"undefined method `foo' for nil:NilClass","b":["prog.rb:5:in `<main>'"]}
Check out ext/json/lib/json/add in the Ruby source to see which classes work this way. If you do require "json/add/core" it will load JSON extensions for Date, DateTime, Exception, OpenStruct, Range, Regexp, Struct, Symbol, Time, and others.

The answer from Jordan is correct. If you have a case eg. that you need to serialize an Exception and send to to ActiveJob where you want to reconstruct it, then you need to use .as_json method.
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json.class
# => String
string = ex.to_json
puts Exception.json_create(string).message
# => m
puts ex.as_json.class
# => Hash
hash = ex.as_json
puts Exception.json_create(hash).message
# => undefined method `foo' for nil:NilClass
You need to read the source code to understand why Exception.json_create(string).messagereturns m :)

It's also important to note that the Exception.json_create example doesn't keep the error class.
Exception.json_create(JSON.parse(ArgumentError.new('error').to_json))
# => #<Exception: error>
Try instead:
require "json/add/exception"
def deserialize_exception(json)
hash = JSON.parse(json)
hash['json_class'].constantize.json_create(hash)
end

Related

What is the method called when the URI('http://google.com') start?

Almost all of you used URI module to convert a url string to an object in order to make some validation or change.
Example:
require 'uri'
URI('https://google.com')
# => #<URI::HTTPS https://google.com>
As you can see, the result is the HTTPS object under the URI module.
So, there is a question what is run when you write module/class name with round braces like the line of code above.
I thought, it is implicit calling of call method, but I got NoMethodError.
Example:
class MyClass
def self.call
puts 'You were right!'
end
end
MyClass()
# => NoMethodError: undefined method `MyClass' for main:Object
Funny enough, with the code you've shown, MyClass.() works (which is an alias for .call).
However in the case of URI, this actually a method (methods can begin with capitals). You can see the source code here: https://apidock.com/ruby/Kernel/URI/instance

Accessing the Receiving Object that Throws an Exception

While debugging a really strange issue with ActionMailer, I came to realize I didn't know how to access an object that was creating the exception. Not the exception, but the object itself.
begin
AppMailer.send_invoice(hostel_resident).deliver_later
flash[:success] = "Your invoice was sent successfully!"
rescue => msg
# display the system generated error message
flash[:error] = "#{msg}"
end
NoMethodError: undefined method `disposition_type' for #<Mail::UnstructuredField:0x009g71c2a68258>
This code works great to catch any exceptions and print the message.
However, how do I get ahold of the <Mail::UnstructuredField:0x009g71c2a68258> object? I'd like to be able to play around with this guy, read messages inside it, and just generally have access to it.
This has to be possible, but inspect doesn't help, cause is no use and backtrace just shows you where it happened. I need that object though, the receiver of the nonexistent method.
Thanks!
actionmailer (4.2.4)
mail (2.6.3)
Seems like you're using mail gem. This is a known issue which is already reported in the GitHub. See #851.
Try using different version of the gem, something in 2.6 series.
This seems to work, using receiver on NameError (which NoMethodError is a child of)
obj = Object.new
puts obj.to_s
begin
obj.do_something
rescue NoMethodError => e
puts e.message
puts e.receiver
end
# #<Object:0x007fa5ac84da88>
# undefined method `do_something' for #<Object:0x007fa5ac84da88>
# #<Object:0x007fa5ac84da88>
This seems to require ruby >= 2.3, to do this for < 2.3, AFAIK you have to do something like this (not tested in older rubies, but should work):
class MyNoMethodError < NoMethodError
attr_accessor :my_receiver
end
obj = Object.new
puts obj.to_s
begin
begin
obj.do_something
rescue NoMethodError => e
# rescue the exception and wrap it in the method that caused it, using `self` instead of `obj`
error = MyNoMethodError.new(e)
error.my_receiver = obj
raise error
end
rescue MyNoMethodError => c
puts c.inspect # custom exception stuff
puts c.cause.inspect # original exception stuff
puts c.my_receiver
end
# #<Object:0x007f884e846d58>
# #<MyNoMethodError: undefined method `do_something' for #<Object:0x007f884e846d58>>
# #<NoMethodError: undefined method `do_something' for #<Object:0x007f884e846d58>>
# #<Object:0x007f884e846d58>

How does the syntax MODULE::METHODNAME('string') work

I recently had cause to use the nokogiri gem to parse html but while i going through their documentation, i came across this ruby syntax that i hadn't seen before
html_doc = Nokogiri::HTML('<html><body><h1>Mr. Belvedere Fan Club</h1></body></html>')
xml_doc = Nokogiri::XML('<root><aliens><alien><name>Alf</name></alien></aliens></root>')
The part of interest for me is Nokogiri::HTML('...'). This looks very much like a method invocation but i know ruby method names cannot be in capital letters. So i looked through code files nokogiri gem and i came across the following definition
module Nokogiri
class << self
###
# Parse HTML. Convenience method for Nokogiri::HTML::Document.parse
def HTML thing, url = nil, encoding = nil, options = XML::ParseOptions::DEFAULT_HTML, &block
Nokogiri::HTML::Document.parse(thing, url, encoding, options, &block)
end
end
# more code
end
I tried reproducing the same code
module How
class << self
def DOESTHISWORK
puts "In How Method"
end
end
end
How::DOESTHISWORK
But it keeps coming back with the error "uninitialized constant How::DOESTHISWORK (NameError)". I know it has to do with the method name starting in capitals but i just haven't been able to figure out how it works in nokogiri.
The difference is in the Nokogiri example the method is being called with parentheses and a parameter value which identifies it as a method call. Your DOESTHISWORK method takes no parameters but can be called with empty parentheses e.g.
irb(main):028:0> How::DOESTHISWORK()
In How Method
=> nil
If you add a parameter to your method that can also serve to identify it as a method like so:
irb(main):036:0> How::DOESTHISWORK 'some param'
Starting method names with a lowercase letter is good practice but isn't enforced. Something that begins with a capital letter is assumed to be a constant and will be looked up as such, this is why the parentheses or parameter is needed to indicate a method is being referred to. Another example:
irb(main):051:0> def Example
irb(main):052:1> puts "An example!"
irb(main):053:1> end
=> nil
irb(main):054:0> Example
NameError: uninitialized constant Example
from (irb):54
from /Users/mike/.rbenv/versions/1.9.3-p194/bin/irb:12:in `<main>'
irb(main):055:0> Example()
An example!
=> nil
I also found this post to be very helpful
What are the restrictions for method names in Ruby?
It's good practice, while not mandatory, to start the method name with
a lower-case character, because names that start with capital letters
are constants in Ruby. It's still possible to use a constant name for
a method, but you won't be able to invoke it without parentheses,
because the interpeter will look-up for the name as a constant

How do I add information to an exception message in Ruby?

How do I add information to an exception message without changing its class in ruby?
The approach I'm currently using is
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.class, "Problem with string number #{i}: #{$!}"
end
end
Ideally, I would also like to preserve the backtrace.
Is there a better way?
To reraise the exception and modify the message, while preserving the exception class and its backtrace, simply do:
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue Exception => e
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
end
end
Which will yield:
# RuntimeError: Problem with string number 0: Original error message here
# backtrace...
It's not much better, but you can just reraise the exception with a new message:
raise $!, "Problem with string number #{i}: #{$!}"
You can also get a modified exception object yourself with the exception method:
new_exception = $!.exception "Problem with string number #{i}: #{$!}"
raise new_exception
I realize I'm 6 years late to this party, but...I thought I understood Ruby error handling until this week and ran across this question. While the answers are useful, there is non-obvious (and undocumented) behavior that may be useful to future readers of this thread. All code was run under ruby v2.3.1.
#Andrew Grimm asks
How do I add information to an exception message without changing its class in ruby?
and then provides sample code:
raise $!.class, "Problem with string number #{i}: #{$!}"
I think it is critical to point out that this does NOT add information to the original error instance object, but instead raises a NEW error object with the same class.
#BoosterStage says
To reraise the exception and modify the message...
but again, the provided code
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
will raise a new instance of whatever error class is referenced by $!, but it will not be the exact same instance as $!.
The difference between #Andrew Grimm's code and #BoosterStage's example is the fact that the first argument to #raise in the first case is a Class, whereas in the second case it is an instance of some (presumably) StandardError. The difference matters because the documentation for Kernel#raise says:
With a single String argument, raises a RuntimeError with the string as a message. Otherwise, the first parameter should be the name of an Exception class (or an object that returns an Exception object when sent an exception message).
If only one argument is given and it is an error object instance, that object will be raised IF that object's #exception method inherits or implements the default behavior defined in Exception#exception(string):
With no argument, or if the argument is the same as the receiver, return the receiver. Otherwise, create a new exception object of the same class as the receiver, but with a message equal to string.to_str.
As many would guess:
catch StandardError => e
raise $!
raises the same error referenced by $!, the same as simply calling:
catch StandardError => e
raise
but probably not for the reasons one might think. In this case, the call to raise is NOT just raising the object in $!...it raises the result of $!.exception(nil), which in this case happens to be $!.
To clarify this behavior, consider this toy code:
class TestError < StandardError
def initialize(message=nil)
puts 'initialize'
super
end
def exception(message=nil)
puts 'exception'
return self if message.nil? || message == self
super
end
end
Running it (this is the same as #Andrew Grimm's sample which I quoted above):
2.3.1 :071 > begin ; raise TestError, 'message' ; rescue => e ; puts e ; end
results in:
initialize
message
So a TestError was initialized, rescued, and had its message printed. So far so good. A second test (analogous to #BoosterStage's sample quoted above):
2.3.1 :073 > begin ; raise TestError.new('foo'), 'bar' ; rescue => e ; puts e ; end
The somewhat surprising results:
initialize
exception
bar
So a TestError was initialized with 'foo', but then #raise has called #exception on the first argument (an instance of TestError) and passed in the message of 'bar' to create a second instance of TestError, which is what ultimately gets raised.
TIL.
Also, like #Sim, I am very concerned about preserving any original backtrace context, but instead of implementing a custom error handler like his raise_with_new_message, Ruby's Exception#cause has my back: whenever I want to catch an error, wrap it in a domain-specific error and then raise that error, I still have the original backtrace available via #cause on the domain-specific error being raised.
The point of all this is that--like #Andrew Grimm--I want to raise errors with more context; specifically, I want to only raise domain-specific errors from certain points in my app that can have many network-related failure modes. Then my error reporting can be made to handle the domain errors at the top level of my app and I have all the context I need for logging/reporting by calling #cause recursively until I get to the "root cause".
I use something like this:
class BaseDomainError < StandardError
attr_reader :extra
def initialize(message = nil, extra = nil)
super(message)
#extra = extra
end
end
class ServerDomainError < BaseDomainError; end
Then if I am using something like Faraday to make calls to a remote REST service, I can wrap all possible errors into a domain-specific error and pass in extra info (which I believe is the original question of this thread):
class ServiceX
def initialize(foo)
#foo = foo
end
def get_data(args)
begin
# This method is not defined and calling it will raise an error
make_network_call_to_service_x(args)
rescue StandardError => e
raise ServerDomainError.new('error calling service x', binding)
end
end
end
Yeah, that's right: I literally just realized I can set the extra info to the current binding to grab all local vars defined at the time the ServerDomainError is instantiated/raised. This test code:
begin
ServiceX.new(:bar).get_data(a: 1, b: 2)
rescue
puts $!.extra.receiver
puts $!.extra.local_variables.join(', ')
puts $!.extra.local_variable_get(:args)
puts $!.extra.local_variable_get(:e)
puts eval('self.instance_variables', $!.extra)
puts eval('self.instance_variable_get(:#foo)', $!.extra)
end
will output:
#<ServiceX:0x00007f9b10c9ef48>
args, e
{:a=>1, :b=>2}
undefined method `make_network_call_to_service_x' for #<ServiceX:0x00007f9b10c9ef48 #foo=:bar>
#foo
bar
Now a Rails controller calling ServiceX doesn't particularly need to know that ServiceX is using Faraday (or gRPC, or anything else), it just makes the call and handles BaseDomainError. Again: for logging purposes, a single handler at the top level can recursively log all the #causes of any caught errors, and for any BaseDomainError instances in the error chain it can also log the extra values, potentially including the local variables pulled from the encapsulated binding(s).
I hope this tour has been as useful for others as it was for me. I learned a lot.
UPDATE: Skiptrace looks like it adds the bindings to Ruby errors.
Also, see this other post for info about how the implementation of Exception#exception will clone the object (copying instance variables).
Here's another way:
class Exception
def with_extra_message extra
exception "#{message} - #{extra}"
end
end
begin
1/0
rescue => e
raise e.with_extra_message "you fool"
end
# raises an exception "ZeroDivisionError: divided by 0 - you fool" with original backtrace
(revised to use the exception method internally, thanks #Chuck)
My approach would be to extend the rescued error with an anonymous module that extends the error's message method:
def make_extended_message(msg)
Module.new do
##msg = msg
def message
super + ##msg
end
end
end
begin
begin
raise "this is a test"
rescue
raise($!.extend(make_extended_message(" that has been extended")))
end
rescue
puts $! # just says "this is a test"
puts $!.message # says extended message
end
That way, you don't clobber any other information in the exception (i.e. its backtrace).
I put my vote that Ryan Heneise's answer should be the accepted one.
This is a common problem in complex applications and preserving the original backtrace is often critical so much so that we have a utility method in our ErrorHandling helper module for this.
One of the problems we discovered was that sometimes trying to generate more meaningful messages when a system is in a messed up state would result in exceptions being generated inside the exception handler itself which led us to harden our utility function as follows:
def raise_with_new_message(*args)
ex = args.first.kind_of?(Exception) ? args.shift : $!
msg = begin
sprintf args.shift, *args
rescue Exception => e
"internal error modifying exception message for #{ex}: #{e}"
end
raise ex, msg, ex.backtrace
end
When things go well
begin
1/0
rescue => e
raise_with_new_message "error dividing %d by %d: %s", 1, 0, e
end
you get a nicely modified message
ZeroDivisionError: error dividing 1 by 0: divided by 0
from (irb):19:in `/'
from (irb):19
from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
When things go badly
begin
1/0
rescue => e
# Oops, not passing enough arguments here...
raise_with_new_message "error dividing %d by %d: %s", e
end
you still don't lose track of the big picture
ZeroDivisionError: internal error modifying exception message for divided by 0: can't convert ZeroDivisionError into Integer
from (irb):25:in `/'
from (irb):25
from /Users/sim/.rvm/rubies/ruby-2.0.0-p247/bin/irb:16:in `<main>'
Here's what I ended up doing:
Exception.class_eval do
def prepend_message(message)
mod = Module.new do
define_method :to_s do
message + super()
end
end
self.extend mod
end
def append_message(message)
mod = Module.new do
define_method :to_s do
super() + message
end
end
self.extend mod
end
end
Examples:
strings = %w[a b c]
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue
raise $!.prepend_message "Problem with string number #{i}:"
end
end
=> NoMethodError: Problem with string number 0:undefined method `do_risky_operation' for main:Object
and:
pry(main)> exception = 0/0 rescue $!
=> #<ZeroDivisionError: divided by 0>
pry(main)> exception = exception.append_message('. With additional info!')
=> #<ZeroDivisionError: divided by 0. With additional info!>
pry(main)> exception.message
=> "divided by 0. With additional info!"
pry(main)> exception.to_s
=> "divided by 0. With additional info!"
pry(main)> exception.inspect
=> "#<ZeroDivisionError: divided by 0. With additional info!>"
This is similar to Mark Rushakoff's answer but:
Overrides to_s instead of message since by default message is defined as simply to_s (at least in Ruby 2.0 and 2.2 where I tested it)
Calls extend for you instead of making the caller do that extra step.
Uses define_method and a closure so that the local variable message can be referenced. When I tried using a class variable ##message, it warned, "warning: class variable access from toplevel" (See this question: "Since you're not creating a class with the class keyword, your class variable is being set on Object, not [your anonymous module]")
Features:
Easy to use
Reuses the same object (instead of creating a new instance of the class), so things like object identity, class, and backtrace are preserved
to_s, message, and inspect all respond appropriately
Can be used with an exception that is already stored in a variable; doesn't require you to re-raise anything (like the solution that involved passing the backtrace to raise: raise $!, …, $!.backtrace). This was important to me since the exception was passed in to my logging method, not something I had rescued myself.
Most of these answers are incredibly convoluted. Maybe they were necessary in Ruby 1.8 or whatever, but in modern versions* this is totally straightforward and intuitive. Just rescue => e, append to e.message, and raise.
begin
raise 'oops'
rescue => e
e.message << 'y daisy'
raise
end
Traceback (most recent call last):
4: from /Users/david/.rvm/rubies/ruby-2.7.2/bin/irb:23:in `<main>'
3: from /Users/david/.rvm/rubies/ruby-2.7.2/bin/irb:23:in `load'
2: from /Users/david/.rvm/rubies/ruby-2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
1: from (irb):2
RuntimeError (oopsy daisy)
* I've only tested with 2.7.2 and 3.1.2, but I assume everything in between is covered, and probably some earlier versions of 2.x as well.
Another approach would be to add context (extra information) about the exception as a hash instead of as a string.
Check out this pull request where I proposed adding a few new methods to make it really easy to add extra context information to exceptions, like this:
begin
…
User.find_each do |user|
reraise_with_context(user: user) do
send_reminder_email(user)
end
end
…
rescue
# $!.context[:user], etc. is available here
report_error $!, $!.context
end
or even:
User.find_each.reraise_with_context do |user|
send_reminder_email(user)
end
The nice thing about this approach is that it lets you add extra information in a very concise way. And it doesn't even require you to define new exception classes inside which to wrap the original exceptions.
As much as I like #Lemon Cat's answer for many reasons, and it's certainly appropriate for some cases, I feel like if what you are actually trying to do is attach additional information about the original exception, it seems preferable to just attach it directly to that exception it pertains to rather than inventing a new wrapper exception (and adding another layer of indirection).
Another example:
class ServiceX
def get_data(args)
reraise_with_context(StandardError, binding: binding, service: self.class, callee: __callee__) do
# This method is not defined and calling it will raise an error
make_network_call_to_service_x(args)
end
end
end
The downside of this approach is that you have to update your error handling to actually use the information that may be available in exception.context. But you would have to do that anyway in order to recursively call cause to get to the root excetion.
It's possible to use :cause key to prevent message duplication
The cause of the generated exception (accessible via Exception#cause) is automatically set to the "current" exception ($!), if any. An alternative value, either an Exception object or nil, can be specified via the :cause argument.
begin
do_risky_operation
rescue => e
raise e.class, "#{e.message} (some extra message)", e.backtrace, cause: nil
end

When is a Ruby class not that Ruby class?

I have this code in my controller for a Rails app:
def delete
object = model.datamapper_class.first(:sourced_id => params[:sourced_id])
if object.blank?
render :xml => "No #{resource} with sourced_id #{params[:sourced_id]}", :status => :not_found and return
end
object.destroy
render :xml => "", :status => :no_content
rescue MysqlError => e
puts "raised MysqlError #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Mysql::Error => e
puts "raised Mysql::Error #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Exception => e
puts "not a MysqlError, instead it was a #{e.class.name}"
render :xml => e.message, :status => :unprocessable_entity and return
end
When I run my spec to make sure my foreign key constraints work, I get this:
not a MysqlError, instead it was a MysqlError
What could be going on here?
Some ancestor information: When I change the rescue to give me this:
puts MysqlError.ancestors
puts "****"
puts Mysql::Error.ancestors
puts "****"
puts e.class.ancestors
This is what I get:
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
MysqlError
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
Could there be an alias in the global namespace that makes the MysqlError class unreachable?
Ruby classes are just objects, so comparison is based on object identity (ie, the same pointer under the hood).
Not sure what's happening in your case, but I'd try debugging in a few locations and seeing what object ids and ancestors you get for MysqlError. I suspect that there's two such objects in different modules and your catch clause is referencing the wrong one.
Edit:
That is quite strange. My guess now is that MysqlError or one of it's ancestors has been included at two different points along your controllers own class chain, and that's somehow tripping up the exception catching.
Theory #2 would be that since rails redefines const_missing to do auto-requires, where you'd expect to get an UndefinedConstant exception in the exception handling clauses is instead finding something by that name god knows where in the source tree. You should be able to see if that's the case by testing with the auto requiring off (ie do some debugs in both dev and prod mode).
There is a syntax for forcing your reference to start from the root which may be of some help if you can figure out the right one to be referencing:
::Foo::Bar
Rant:
This sort of thing is where I think some of the flaws of ruby show. Under the hood Ruby's object model and scoping is all object structures pointing to each other, in a way that's quite similar to javascript or other prototype based languages. But this is surfaced inconsistently in the class/module syntax you use in the language. It seems like with some careful refactoring you could make this stuff clearer as well as simplify the language, though this would of course be highly incompatible with existing code.
Tip:
When using puts for debugging, try doing puts foo.inspect as this will display it in the way you're used to from irb.
This was a simple class redefinition bug. Ruby lets you redefine a top-level constant, but it doesn't destroy the original constant when you do it. Objects that still hold references to that constant can still use it, so it could still be used to generate exceptions, like in the issue I was having.
Since my redefinition was happening in dependencies, I solved this by searching for the original class in the Object space, and hanging on to a reference to it to use when catching exceptions. I added this line to my controller:
ObjectSpace.each_object(Class){|k| ##mysql_error = k if k.name == 'MysqlError'}
That gets a reference to the original version of MysqlError. Then I was able to do this:
rescue ##mysql_error => e
render :xml => e.message, :status => :unprocessable_entity and return
This happens because the mysql gem is getting loaded after MysqlError has already been defined. Here is some test console joy:
Loading test environment (Rails 2.3.2)
>> MysqlError.object_id
=> 58446850
>> require 'mysql'
C:/Ruby/lib/ruby/gems/1.8/gems/mysql-2.7.3-x86-mswin32/ext/mysql.so: warning: already initialized constant MysqlError
=> true
>> MysqlError.object_id
=> 58886080
>> ObjectSpace._id2ref(MysqlError.object_id)
=> Mysql::Error
You can do this in IRB without a require pretty easily; here's a trick that works because irb doesn't look up Hash by name every time you declare a Hash literal:
irb(main):001:0> Hash = Class.new
(irb):1: warning: already initialized constant Hash
=> Hash
irb(main):002:0> hash = {:test => true}
=> {:test=>true}
irb(main):003:0> hash.class
=> Hash
irb(main):004:0> hash.is_a? Hash
=> false
I can see why you might want to do this, it could be used like alias_method_chain for the global namespace. You could add a mutex to a class that isn't threadsafe, for example, and not need to change old code to reference your threadsafe version. But I do wish RSpec hadn't silenced that warning.

Resources