Logging Internal Server Errors

A good rule of thumb for websites is that for every hundred users who runs into a bug, one will let you know. This is when the user can even recognize there’s an error because they get a big “500: Internal Server Error”. You might get more reports if you have a very small userbase who know you, but don’t count on it. You have to log errors if you want to find out about them, so here’s some code to do that in Rails.

By default Rails logs all accesses and errors to production.log, but I decided to save errors to individual files to save the hassle of trying to sift them out of that haystack. I considered logging them to a database table, mailing them, or providing an RSS feed but in the end decided to punt on it. Every added bit of complexity in rescue_error_in_public is another opportunity for something else to go wrong and the error to be lost. It’s easy to write a cron job or other service to pick them up from files and do something useful with them.

Save this file as lib/handle_errors.rb:

 
module ActionController
module Rescue
  def rescue_action_in_public(exception)
    case exception
      when ::ActionController::RoutingError, ::ActionController::UnknownAction
        render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
      else
        render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
        if defined? ERROR_PATH
          File.open(File.join(ERROR_PATH, Time.now.strftime("exception_%Y-%m-%d_%H:%M:%S_#{rand(99999)}")), 'w') do |file|
            try_printing(file, "request") { file.print "Request to #{request.request_uri} from #{request.remote_ip}nn" }
            try_printing(file, "params") { file.print "Params #{params.to_yaml}nn" }
            try_printing(file, "exception") { file.print(
              "#{exception.class} (#{exception.message}):n    " +
              clean_backtrace(exception).join("n    ") +
              "nn"
            ) }
            try_printing(file, "session") { file.print "Session #{session.to_yaml}nn" }
          end
        end
    end
  end
 
#  def rescue_action_locally(exception)
#    rescue_action_in_public(exception)
#  end
 
  def try_printing file, title
    begin
      yield
    rescue Exception => exception
      file.print "Failed to print #{title}: #{exception.class} (#{exception.message})nn"
    end
  end
end
end

Edit your config/environments.rb to require 'handle_errors' and, in your specific config/environment files, add ERROR_PATH = '/path/to/save'. You can see this working in development by uncommenting the rescue_action_locally.

Security note: If you're dealing with credit cards or other sensitive financial or personal data, you'll want to scrub that from params and session before logging.

Want more? I'm not as good at forgetting to update @pushcx on Twitter.