Exception handling for Rails 3
Ruby JavaScript
Pull request Compare This branch is 1 commit ahead, 153 commits behind Sharagoz:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Rails Exception Handler Build Status

This is an exception handler for Rails 3 built as Rack middleware. It enables you to save the key information from the error message in a database somewhere and display a customized error message to the user within the applications layout file. You can hook this gem into all your rails apps and gather the exception reports in one place. This gem just contains the back end. You will have to create your own front end to view and manage the error reports.

Does your app have an authorization mechanism? See wiki Do you need to catch ruby errors outside of Rack? See wiki


The gem should work with all versions of Rails 3. It does not work with Rails 2. See Travis-CI for info on which rubies it is tested against: http://travis-ci.org/#!/Sharagoz/rails_exception_handler


Add this line to your Gemfile:

gem 'rails_exception_handler'

Create an initializer in config/initializers called rails_exception_handler.rb and uncomment the options where you want something other than the default:

RailsExceptionHandler.configure do |config|
  # config.environments = [:development, :test, :production]                # Defaults to [:production]
  # config.storage_strategies = [:active_record, :rails_log, :remote_url => {:target => 'http://example.com'}] # Defaults to []
  # config.fallback_layout = 'home'                                         # Defaults to 'application'
  # config.store_user_info = {:method => :current_user, :field => :login}   # Defaults to false
  # config.filters = [                                                      # No filters are  enabled by default
  #   :all_404s,
  #   :no_referer_404s,
  #   :anon_404s,
  #   {:user_agent_regxp => /\b(ApptusBot|TurnitinBot|DotBot|SiteBot)\b/i},
  #   {:target_url_regxp => /\b(myphpadmin)\b/i}
  # ]
  # config.responses = {                                                    # There must be a default response if public/500.html and public/400.html does not exist.
  #   :default => "<h1>500</h1><p>Internal server error</p>",
  #   :custom => "<h1>404</h1><p>Page not found</p>"
  # }
  # config.response_mapping = {                                             # All errors are mapped to the :default response unless overridden here
  #  'ActiveRecord::RecordNotFound' => :custom,
  #  'ActionController::RoutingError' => :custom,
  #  'AbstractController::ActionNotFound' => :custom
  # }
  # config.after_initialize do
  #   # this block will be called after the initialization is done
  # end

Configuration options explained


An array of symbols that says which Rails environments you want the exception handler to run in.

config.environments = [:production, :test, :development]

Default value: [:production]


An array of zero or more symbols that says which storage strategies you want to use. Each are explained in detail in separate sections at the end of this document.

config.storage_strategies = [:active_record, :rails_log, :remote_url => {:target => 'http://example.com'}]

Default value: []


config.fallback_layout = 'home'

Default value: 'application'

The exception handler will always use the layout file of the controller action that was accessed when the error occured. However, when routing errors occures there are no controller action to get this layout from, so it falls back to the default 'application' layout that most apps have. If your application does not have a layout file called 'application', then you need to override this, otherwise a "missing layout" exception will occur.


Having some way of identifying the user can be very useful at times, so I always store information on who generated the exception in applications that have a log in feature. To enable this you need to specify the name of the controller method that provides the user object and the name of the field which contains the info you want to save.

config.store_user_info = {:method => :current_user, :field => :login}

Default value: false (no info will be stored) If you turn this on and the error is generated by a client that is not logged in, then "Anonymous" will be used.

responses and response_mapping

Note: public/500.html and public/400.html will be used if these exists. Remove these files before applying the configuration below.

Create a set of responses and then map specific exceptions to these responses. There needs to be a response called :default which is used for the exceptions that are not explicity mapped to a response.

config.responses = {
  :default => "<h1>500</h1><p>Internal server error</p>",
  :not_found => "<h1>404</h1><p>Page not found</p>",
  :wrong_token => "<h1>500</h1><p>There was a problem authenticating the submitted form. Reload the page and try again.</p>",
  :teapot => "<h1>418</h1><p>I'm a teapot</p>"
config.response_mapping = {
 'ActiveRecord::RecordNotFound' => :not_found,
 'ActionController:RoutingError' => :not_found,
 'AbstractController::ActionNotFound' => :not_found,
 'ActionController::InvalidAuthenticityToken' => :wrong_token,
 'Teapot::CoffeeGroundsNotSupported' => :teapot


This callback exists in case you need to do something right after the initializer has been run, for instance interact with an authorization mechanism


All filters are disabled by default. I recommend you deploy your application this way, and then add filters as they become necessary. The only reason I've ever wanted filtering have been due to what seem like poorly programmed web crawlers and black bots probing for security holes. If legitimate web crawlers are a problem for you, look into tweaking your robots.txt file before enabling exception filters.


config.filters = [:all_404s]

When turned on the following exceptions will no longer be stored: ActionController::RoutingError, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound Consider this a last resort. You will miss all "real" 404s when this is turned on, like broken redirections.


config.filters = [:anon_404s]

When turned on the following exceptions will no longer be stored unless a user is logged in: ActionController::RoutingError, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound


config.filters = [:no_referer_404s]

ActionController::RoutingError, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound will be ignored if it was caused by a request without a referer. This is very effective against bots. 99.9% of the time a routing error with no referer will be caused by a bot, and then once in a while it will be caused by a real user that happened to generate an error on the first page he opened. You will get a lot less false positives with this filter than :all_404s.


Legit software will usually add something to the user agent string to let you know who they are. You can use this to filter out the errors they generate, and be pretty sure you are not going to get any false positives.

config.filters = [:user_agent_regxp => /\b(ZyBorg|Yandex|Jyxobot)\b/i]

If you (like me) dont know regular expressions by heart, then http://www.rubular.com/ is great tool to use when creating a regxp.


Sometimes black bots add a common user agent string and a referer to their requests to cloak themselfs, which makes it hard to filter them without filtering all routing errors. What you can often do is to filter on what they target, which is usually security holes in some well known library/plugin (usually in php). The example below will filter out all URLs containing ".php". This is the filter I most commonly use myself. Without it, it is only a matter of time before I'll one day get 200 exceptions in 10mins caused by a bot looking for security holes in myPhpAdmin or some other PHP plugin.

config.filters = [:target_url_regxp => /\.php/i]

Storage strategy - active record

config.storage_strategies = [:active_record]

This means that the error reports will be stored through active record directly to a database, which is pretty much the whole reason why I created this library in the first place. A new entry called exception_database is needed in database.yml:

# for mysql the entry would look something like this:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: your_database
  pool: 5
  username: user
  password: secret

You could of course store the error messages in the same database as the application uses, but one of the main purposes of this library is to enable you to easily store error reports from many applications in the same database, so I recommend you set up a separate dedicated database for this.

The exception database needs a table called error_messages. Here's a migration script that you can use to create the table with the necessary fields:

class CreateErrorMessages < ActiveRecord::Migration
  def self.up
    create_table :error_messages do |t|
      t.text :class_name
      t.text :message
      t.text :trace
      t.text :params
      t.text :target_url
      t.text :referer_url
      t.text :user_agent
      t.string :user_info
      t.string :app_name


  def self.down
    drop_table :error_messages

Storage strategy - rails log

config.storage_strategies = [:rails_log]

An error will be logged in the standard rails log. The log i located in the RAILS_ROOT/log directory and is named after the Rails environment. Example of what a report might look like:

TARGET:     http://example.com/users
REFERER:    http://example.com/
PARAMS:     {"controller"=>"users", "action"=>"index"}
USER_AGENT: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30
USER_INFO:  matz
ActionView::Template::Error (ActionView::Template::Error):
activesupport (3.0.7) lib/active_support/whiny_nil.rb:48:in `method_missing'
actionpack (3.0.7) lib/action_view/template.rb:135:in `block in render'
activesupport (3.0.7) lib/active_support/notifications.rb:54:in `instrument'
(the rest of the stack trace has been omitted from the example)

Storage strategy - remote url

config.storage_strategies = [:remote_url => {:target => 'http://example.com/error_messages'}]

This option is meant for those who want to store the exception in a database table, but does not have direct access to the database itself, making active record store unsuitable. You need a web app on a server that has access to the database. An HTTP POST request will be sent to the specified URL with the error message as data. If you use a Rails app at the other end you should simply be able to do ErrorMessage.create(params[:error_message]) to save the report.


David Rice

Would you like to contribute? Here are some things on the todo list:

  • A mongoid storage strategy for those that wish to use MongoDB
  • An email storage strategy for those that wish to be notified of the exceptions through email


Copyright © 2011 Bjørn Trondsen, released under the MIT license