Tuesday, July 21, 2009

Sending mail to developer about exception errors

I want my application to send me a mail about any errors that occurs during production. Basically, I want to see a detail report like in development environment. All I have to do is to override rescue_action_in_public. This method by default calls render_optional_error_file method to render a static page based on status code thrown. rescue_action_locally method by default will render details diagnostics from a controller action. Therefore, I just combine all of these methods into rescue_action_in_public and add some code to send mail.

def rescue_action_in_public(exception)
   render_optional_error_file response_code_for_rescue(exception)

   @template.instance_variable_set("@exception", exception)
   @template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
   @template.instance_variable_set("@contents",
   @template.render(:file => template_path_for_local_rescue(exception)))

# send mail to developers
   mail = ExceptionNotifier.create_sent(@template)
   mail.set_content_type("text/html")
   ExceptionNotifier.deliver(mail)
end

Here is my ExceptionNotifier class, just extract needed variable to be available inside my view.

class ExceptionNotifier < ActionMailer::Base

   def sent(template)
      @subject = 'Bug Reports'
      @body["request"] = template.request
      @body["response"] = template.response
      @body["exception"] = template.instance_variable_get("@exception")
      @body["rescues_path"] = template.instance_variable_get("@rescues_path")
      @recipients = ['chamnapchhorn@gmail.com', 'ungsophy@gmail.com']
      @from = 'noreply@gmail.com'
      @headers = {}
   end

end

Here is my view, the default view for exception. I just change to locate new variables only.

<h1>
   <%=h @exception.class.to_s %>
      <% if @request.parameters['controller'] %>
         in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
      <% end %>
</h1>
<pre><%=h @exception.clean_message %></pre>

<%= render :file => @rescues_path["rescues/_trace.erb"] %>

<%= render :file => @rescues_path["rescues/_request_and_response.erb"], :locals => { :request => @request, :response => @response } %>

Friday, July 10, 2009

Handling Exception at the Application Level in Rails

At last the point of the project, I found one more important thing that is quite useful and it could reduce the code 20%. That's when I did a handling service error on both server and client. I find myself have been repeating the code on the way. Luckily, after discussion with my colleague, I came up with idea to handle in application controller. It means that instead of handle exception on all of my controllers, I could just handle in a single place whenever exception has raised from these controllers.
Basically, there are two methods (rescue_action and rescue_action_in_public) that you would need to override based on your needs. By default, these two methods do the best job to handle exception both in development and production mode. rescue_action method will be called with an exception parameter that raises inside an action method. rescue_action_in_public method, however, is used for public exception handling (for requests answering false to local_request?). local_request? method tells which rescue_*** method to call.

http://api.rubyonrails.org/classes/ActionController/Rescue.html

More importantly, we can handle exceptions for specific controller instead of the whole. All you need to do is to override one of these methods inside that controller.

class PostsController < ApplicationController
   def rescue_action_in_public(exception)
      case(exception)
         when ActiveRecord::RecordNotFound then render :file => '/bad_record'
         when NoMethodError then render :file => '/no_method'
         else render :file => '/error'
      end
   end
end


We can even handle them in a much cleaner way rather than if/else statement by using rescue_from. What it does is that it maps an exception type to handler method. This handler method can take either an exception parameter or a non-argument. We can even specify a proc or block.

class PostsController < ApplicationController

   # Declare exception to handler methods
   rescue_from ActiveRecord::RecordNotFound, :with => :bad_record
   rescue_from NoMethodError, :with => :show_error

   def bad_record; render :file => '/bad_record'; end
   def show_error(exception); render :text => exception.message; end

end

http://ryandaigle.com/articles/2007/9/24/what-s-new-in-edge-rails-better-exception-handling

Subscribe in a Reader