• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Ruby & Rails Error Handling
 

Ruby & Rails Error Handling

on

  • 11,329 views

Some tips on handling errors in both Ruby and Rails

Some tips on handling errors in both Ruby and Rails

Statistics

Views

Total Views
11,329
Views on SlideShare
11,327
Embed Views
2

Actions

Likes
14
Downloads
49
Comments
1

2 Embeds 2

http://ams.activemailservice.com 1
https://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Wrong links on slides 48 and 49
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • 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 Ruby & Rails Error Handling Presentation Transcript

  • Error Handling in Ruby & Rails Simon Maynard - Bugsnag CTO @snmaynard
  • 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
  • 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 = Account.new acccount.name #!?
  • 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! account.save
  • 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
  • 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
  • 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
  • Raise Syntax raise MyError.new("Something Broke")is the same as raise MyError, "Something Broke"
  • Raise Syntax raise "Something Broke"is the same as raise RuntimeError, "Something Broke"
  • Raise Syntax raiseis the same as raise RuntimeError
  • Raise SyntaxYou can also pass a backtrace when raising an exception def assert(value) raise(RuntimeError, "Something broke", caller) unless value end
  • 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
  • How does raise build the exception?You might think that raise does this def raise(klass, msg, trace) exception = klass.new(message) # ... endBut actually it does this... def raise(klass, msg, trace) exception = klass.exception(message) # ... end
  • How does raise build the exception?• Exception.exception • The same as Exception.new()• Exception#exception • With no arguments, it returns self • With a message, it returns a new exception with the message set
  • How does raise build the exception?This means we can implement our own exception methods class Account def exception(message="Bad Account!") ValidationError.new("#{message}: #{self.errors.inspect}") end endthen we can throw an instance of own object raise account unless account.save
  • 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
  • rescue Syntax begin rescue MyError => error endwill rescue all MyError exceptions
  • rescue Syntax begin rescue => error endis the same as begin rescue StandardError => error end
  • Rescue Syntax begin rescue endis the same as begin rescue StandardError end
  • Rescue SyntaxYou can also supply a list of classes to rescue begin rescue MyError, IOError => error end
  • One Line Rescue Syntax value = raise rescue "fallback_value"is the same as value = begin raise rescue "fallback_value" end
  • Dynamic rescuesdef match_message(regex) mod = Module.new (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
  • Re-raising exception begin raise rescue raise endis the same as begin raise rescue raise $! end
  • 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
  • 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!
  • 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 MyError.new("Something broke") end
  • 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
  • ALTERNATIVE SyntaxYou can also use these commands without a begin section def func(arg) raise rescue # Deal with exception ensure # Always run end
  • Ensure SyntaxBe careful with return inside an ensure! def func(arg) raise ensure # This return swallows the exception return 5 end
  • 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
  • exception hierarchy ExceptionNoMemoryError ScriptError SignalException SystemExit StandardError fatal Interrupt LoadError ArgumentError SyntaxError IOError ... IndexError ...
  • 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!
  • 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...
  • Raise is a method• Raise is just a method on Kernel• So we can override it!
  • 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
  • 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: Time.now.utc, message: $!.message, trace: $!.backtrace, } log_file.write(error.to_json) end end end
  • 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
  • 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
  • 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)
  • RACK Middleware Request Response Middleware 1 Middleware 2 Middleware 3 Middleware 4 Rails App
  • Example RACK MiddlewareHere is an example of a no-op middleware module OurMiddleware class Rack def initialize(app) @app = app end def call(env) @app.call(env) end end end
  • 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
  • Example RACK Middleware• Call is called for every request• @app.call calls the next middleware in the stack (or your app itself) def call(env) response = @app.call(env) end
  • Rendering a 500 page• Rails uses this to handle errors, for example in ShowExceptions middleware def call(env) @app.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
  • Bugsnag Logging middleware• Here is a simplified version of the Bugsnag error logging middleware def call(env) @app.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!
  • 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
  • Better Errors• https://github.com/charliesome/better_errors• Better version of DebugExceptions, used in development on Rails• Allows you to debug crashes when they happen
  • Hammertime• https://github.com/avdi/hammertime• Allows you to debug exception raises in real time in Ruby apps
  • PRY RESCUE• https://github.com/ConradIrwin/pry-rescue• Allows you to debug uncaught exceptions in real time in Ruby apps
  • Bugsnag• http://bugsnag.com• Tracks and groups your errors from development and production• Get notified when someone on production sees a crash!
  • Find out more• Avdi Grimm has a great book on Ruby failure handling - I highly recommend it (http://exceptionalruby.com/)• When looking into rails error handling, delving into Rails source is recommended.
  • Questions?Check out www.bugsnag.com