Ruby & Rails Error Handling


Published on

Some tips on handling errors in both Ruby and Rails

Published in: Technology
1 Comment
  • Wrong links on slides 48 and 49
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • experience of monitoring apps at scale heyzap
  • example errors
  • Performance
  • Return an error value if its common or people will use to test Validation function shouldnt raise if invalid e.g mongoid find raising by default, at least you can change if you want
  • raise = fail raise common some use fail to be when failing for first time, raise in rescue blocks
  • exception is like to_s, but for exceptions
  • Can attach exception methods to objects easily, so they can be raised
  • These are just examples, empty rescue blocks are a code smell, at least have a log in there!
  • Bad as it catches all StandardErrors, you should catch explicit exception types, more on that later
  • Rescue calls === on the error and the object passed to rescue, so we can override
  • This is a common pattern in ruby, getting more popular - rails does it when you have an error in your view for example converts to template error
  • Exception hierarchy shows why you should never catch exception
  • Be specific when you catch something
  • Note here I rescue exception, but I always re-raise :-)
  • Sinatra uses this when using etags i believe, to prevent recalculating a still valid response
  • Middleware performance is important Crashes in middleware are very bad
  • Ruby & Rails Error Handling

    1. 1. Error Handling in Ruby & Rails Simon Maynard - Bugsnag CTO @snmaynard
    2. 2. What is Bugsnag?• We help developers log and track their errors• Ruby was the first language we supported• Now support Python, PHP, Javascript, Node.js, iOS, Android & more!• Processing millions of errors every day
    3. 3. What is AN ERROR?An error occurs when code is unable to complete the task asked of it. • Asked to do the impossible Account.find(nil) • A mistake in the code account = #!?
    4. 4. What is AN ERROR?• An unexpected case case account.type when "free" then # when "paid" then # else # Unexpected! end• Failure of an external element # Database down!
    5. 5. How to report an error?• Raise an exception • Only when the error is truly exceptional • If the error is unexpected, or a sign of bad code • Raising should be unexpected, it’s slow
    6. 6. How to report an error?• Return an error value • If its to be expected or part of "normal" operationdef find(id) raise InvalidIdError, "#{id} id an invalid id" unless validate_id(id) return data[id]end
    7. 7. Raise Or Fail• You can use raise to raise an exception.• You can also use fail to raise an exception. def func fail "Not implemented" rescue => e raise unless e.message == "Not implemented" end
    8. 8. Raise Syntax raise"Something Broke")is the same as raise MyError, "Something Broke"
    9. 9. Raise Syntax raise "Something Broke"is the same as raise RuntimeError, "Something Broke"
    10. 10. Raise Syntax raiseis the same as raise RuntimeError
    11. 11. Raise SyntaxYou can also pass a backtrace when raising an exception def assert(value) raise(RuntimeError, "Something broke", caller) unless value end
    12. 12. What does raise actually do?• Raise does four things, • Builds an exception object • Sets the backtrace • Sets the global error object ($!) • Starts unwinding the stack
    13. 13. How does raise build the exception?You might think that raise does this def raise(klass, msg, trace) exception = # ... endBut actually it does this... def raise(klass, msg, trace) exception = klass.exception(message) # ... end
    14. 14. How does raise build the exception?• Exception.exception • The same as• Exception#exception • With no arguments, it returns self • With a message, it returns a new exception with the message set
    15. 15. How does raise build the exception?This means we can implement our own exception methods class Account def exception(message="Bad Account!")"#{message}: #{self.errors.inspect}") end endthen we can throw an instance of own object raise account unless
    16. 16. Global Error Object$! contains a reference to the exception currently being raised begin raise rescue puts $!.inspect endYou can also require “english” to use the slightly more readable$ERROR_INFO require "english" begin raise rescue puts $ERROR_INFO.inspect end
    17. 17. rescue Syntax begin rescue MyError => error endwill rescue all MyError exceptions
    18. 18. rescue Syntax begin rescue => error endis the same as begin rescue StandardError => error end
    19. 19. Rescue Syntax begin rescue endis the same as begin rescue StandardError end
    20. 20. Rescue SyntaxYou can also supply a list of classes to rescue begin rescue MyError, IOError => error end
    21. 21. One Line Rescue Syntax value = raise rescue "fallback_value"is the same as value = begin raise rescue "fallback_value" end
    22. 22. Dynamic rescuesdef match_message(regex) mod = (class << mod; self; end).instance_eval do define_method(:===) do |e| regex === e.message end end modendbegin raise "sample message"rescue match_message(/sample/) # Ignoreend
    23. 23. Re-raising exception begin raise rescue raise endis the same as begin raise rescue raise $! end
    24. 24. Raising in rescueYou can also change the exception message before re-raising begin raise rescue => err # Re raise with different message raise err, “Different message” end
    25. 25. Raising in rescueYou can raise in a rescue def func raise rescue raise "totally new exception" endYou lose the context of the real error!Don’t do this!
    26. 26. Raising in rescueInstead you can keep a reference to the original exception class MyError < StandardError attr_accessor :original_exception def initialize(msg, original_exception=$!) super(msg) self.original_exception = original_exception end end def func raise rescue raise"Something broke") end
    27. 27. Ensure SyntaxEnsure allows you to ensure that code is run, regardless of whether an exceptionis raised or not. begin raise unless rand < 0.5 ensure # Always run end
    28. 28. ALTERNATIVE SyntaxYou can also use these commands without a begin section def func(arg) raise rescue # Deal with exception ensure # Always run end
    29. 29. Ensure SyntaxBe careful with return inside an ensure! def func(arg) raise ensure # This return swallows the exception return 5 end
    30. 30. RETRYYou can also easily retry using the retry keyword def func(arg) attempts = 0 begin attempts += 1 raise rescue retry if attempts < 3 end end
    31. 31. exception hierarchy ExceptionNoMemoryError ScriptError SignalException SystemExit StandardError fatal Interrupt LoadError ArgumentError SyntaxError IOError ... IndexError ...
    32. 32. Exception hierarchyFor example, while true do begin line = STDIN.gets # heavy processing rescue Exception => e puts "caught exception #{e}! ohnoes!" end endThis program is almost unkillable! Don’t catch Exception!
    33. 33. Exception HierarchyYou can even prevent an exit call, begin exit(1) # Or abort() rescue Exception puts "Guess again!" end # Continue...You can’t catch an exit!(1) however...
    34. 34. Raise is a method• Raise is just a method on Kernel• So we can override it!
    35. 35. Raise is a methodWe can add debugging information to each raise module RaiseDebug def raise(*args) super *args rescue Exception puts "Raising exception: #{$!.inspect}" super *args end end class Object include RaiseDebug end
    36. 36. Uncaught ErrorsWe can use a combination of $! and the ruby exit handler to log uncaught errors at_exit do if $! open(error.log, a) do |log_file| error = { timestamp:, message: $!.message, trace: $!.backtrace, } log_file.write(error.to_json) end end end
    37. 37. Throw/CatchRuby can also throw, but its not for errors.Use throw to unwrap the stack in a non-exceptional case, saves you from usingmultiple break commands INFINITY = 1.0 / 0.0 catch (:done) do 1.upto(INFINITY) do |i| 1.upto(INFINITY) do |j| if some_condition throw :done end end end end
    38. 38. How does rails deal with exceptions?When there is an error in your rails app, ideally we want these things to happen • 500 page rendered to show the user something went wrong • Error logged with enough information so we can fix it • Rails to continue serving requests
    39. 39. How does rails deal with exceptions?• Rails uses a Rack app to process every request.• Rack apps have a middleware stack• You can easily add your own middleware so you can execute code on every request config.middleware.use(new_middleware, args) config.middleware.insert_before(existing_middleware, new_middleware, args) config.middleware.insert_after(existing_middleware, new_middleware, args) config.middleware.delete(middleware)
    40. 40. RACK Middleware Request Response Middleware 1 Middleware 2 Middleware 3 Middleware 4 Rails App
    41. 41. Example RACK MiddlewareHere is an example of a no-op middleware module OurMiddleware class Rack def initialize(app) @app = app end def call(env) end end end
    42. 42. Example RACK Middleware• Initialize called when rails app starts• Takes a single parameter, which is the next middleware in the stack• Perform any other initialization for your middleware def initialize(app) @app = app end
    43. 43. Example RACK Middleware• Call is called for every request• calls the next middleware in the stack (or your app itself) def call(env) response = end
    44. 44. Rendering a 500 page• Rails uses this to handle errors, for example in ShowExceptions middleware def call(env) rescue Exception => exception raise exception if env[action_dispatch.show_exceptions] == false render_exception(env, exception) end• Rails rescues the exception here, and renders a nice 500 error page
    45. 45. Bugsnag Logging middleware• Here is a simplified version of the Bugsnag error logging middleware def call(env) rescue Exception => exception Bugsnag.notify(exception) raise end• But you need to make sure this goes in you middleware stack JUST before you render the 500 page!
    46. 46. SHOW The middleware stack• Rails has given you an awesome tool to show your middleware stack $ rake middleware ... use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use Bugsnag::Rack ... use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser ... run YourApp::Application.routes
    47. 47. Better Errors•• Better version of DebugExceptions, used in development on Rails• Allows you to debug crashes when they happen
    48. 48. Hammertime•• Allows you to debug exception raises in real time in Ruby apps
    49. 49. PRY RESCUE•• Allows you to debug uncaught exceptions in real time in Ruby apps
    50. 50. Bugsnag•• Tracks and groups your errors from development and production• Get notified when someone on production sees a crash!
    51. 51. Find out more• Avdi Grimm has a great book on Ruby failure handling - I highly recommend it (• When looking into rails error handling, delving into Rails source is recommended.
    52. 52. Questions?Check out