Basic Exception notification in Rails 3

Thoughts from the team
By    | February 10, 2012 | Development, Tips & Tricks, Tools & Plugins, Tutorials,

There are a number of services out there, like Airbrake, that will capture exceptions from a Rails app and do clever stuff with them. They let you manage your exceptions and track any work that results from them.  But they all cost money, and might be overkill for what you are doing. So here is a quick guide to making your Rails application email you when exceptions happen, and how to display better error pages to your users.

I’m assuming that you already have email delivery configured.  If you don’t, then get that sorted first, and make sure it works.

Setting up message delivery

This all relies on the Exception Notification gem so add that to your Gemfile and run bundle

# Exception notification
gem 'exception_notification'

Now configure Exception Notification in your environments. You probably only want to use it in your production environment, but you may want to test it in your development environments.  So in config/environments/production.rb put something like this (season to taste)…

config.middleware.use ExceptionNotifier,
  :email_prefix => "[My Application Error Report] ",
  :sender_address => %{"My App" <app@example.com>},
  :exception_recipients => %w{paul@example.com}

You’ll probably want to make the sender and recipient addresses configurable as environment variables. The :email_prefix is appended to the subject of the message.

Exception handling and sending the message

In application_controller.rb, add the following near the top…

unless Rails.application.config.consider_all_requests_local
  rescue_from Exception,
              :with => :render_error
  rescue_from ActiveRecord::RecordNotFound,
              :with => :render_not_found
  rescue_from ActionController::RoutingError,
              :with => :render_not_found
  rescue_from ActionController::UnknownController,
              :with => :render_not_found
  rescue_from ActionController::UnknownAction,
              :with => :render_not_found
end

The rescue_from method tells Rails what to do when certain exceptions are raised and make it all the way to the top of the controller call stack without getting caught.  In this case we tell it to call one of two controller methods, render_error (for errors) and render_not_found (for all the various ways you can not find something).

You’ll need to define the two methods in a protected block in application_controller.rb

def render_not_found(exception)
  render :template => "/errors/404.html.erb",
         :layout => 'errors.html.erb'
end

def render_error(exception)
  ExceptionNotifier::Notifier
    .exception_notification(request.env, exception)
    .deliver
  render :template => "/errors/500.html.erb",
         :layout => 'errors.html.erb'
end

Create some templates for displaying a nice error message to your users, and place them in app/views/errors and the layout errors.html.erb (assuming you are using ERB).  You can make these as nice as you like.  These will be rendered by the two methods.

In addition, render_error will also send you an email using ExceptionNotifier and the configuration we set up earlier.

If you wanted to you could also send alerts from render_not_found.  This could be useful for notifying you of broken links, but is also likely to generate a lot of crap as search engines, crawlers and malware try to load certain pages speculatively.

Caveat for Rails 3

If you are using Rails 2 everything should be working fine now.  If you are using Rails 3 then we have one more step to get it working right.

As of Rails 3 errors in routing, for example if you try to access a URL that has no route associated with it, are no longer handled by ApplicationController, they are dealt with by the middleware.  If you want to handle them in the same way as all your other errors then we have to do a bit more work.

At the very bottom of config/routes.rb, add something like the following:

# Catch unroutable paths and send to the routing error handler
match '*a', :to => 'home#routing_error'

This will route all unmatched routes to my HomeController's routing_error action. You could put this action anywhere you like. You could create a controller specifically for handling errors like this, or put it in whatever controller handles your root (which is what I’ve done).

Your routing_error action simply needs to call the render_not_found method from ApplicationController

def routing_error
  render_not_found(nil)
end

And that’s it. Exceptions will get sent to you to deal with, and your user’s get nicer error messages.

More like this