Table of Contents
I wrote code to send mail when error occurs in Rails 4.
Environment
- Ubuntu 15.05 (64bit)
- Ruby 2.2.2
- Rails 4.1.8
Old Way
In Rails 3, I could use rescue_in_public
method in ApplicationController
and send error notification mail.
1 2 3 4 5 6 7 8 9 10 11 12 |
class ApplicationController < ActionController::Base protected def rescue_action_in_public(exception) SystemMailer.system_error( current_user, self, request, exception).deliver super(exception) end def local_request? false end end |
current_user
returns user who logging in. It’s defined in devise gem.
But in rails 4, it doesn’t work.
Solution
I used rescue_from
, which also exists in Rails 3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class ApplicationController < ActionController::Base rescue_from Exception, :handle_exception private # handle exception # ==== Parameter # * +exception+ def handle_exception(exception) send_error_mail(exception) raise exception end # send error mail # ==== Parameter # * +exception+ def send_error_mail(exception) if !Rails.env.development? SystemMailer.error_mail( exception, current_user, self, request) end end end |
rescue_from Exception
catches all exception occurs in controller, but rescue_from StandardError
doesn’t. It is because RuntimeError
, for example, is inherits Exception
but doesn’t inherit StandardError
, which is raised by raise "exception!"
simply. And, syntax error like 0 = 1
couldn’t be caught. Error caught by rescue_from Exception
is errors occurs in running program. And errors occur before controller procedure can’t be caught.
The point is raise exception
in handle_exception
method. I don’t want to erase exception by handle_exception
, but want only to send notification mail. It is like AOP. In the above, add method into application_controller.rb
simply.
current_user
in send_error_mail
method returns logging in user, which is defined in the gem, devise. self
is used for reporting in which controller the error occurs. request
is for reporting request url, etc. The code !Rails.env.development?
should be replace to config
value. I heard that we can use custom configuration in Rails 4.2, so it is the issue after rails upgrading.
Attention
Exceptions before controller procedure should be reported by other way.
I know handling exception outside of controller is a way, but there are variables can be referred in controller and objects useful in controller, I thought, so I wrote code into applicatoin_controller.rb
.
Error Reporting Mail
Mailer Class
First, execute rails g mailer SystemMailer system_error
and generate SystemMailer
class, and change it to the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class SystemMailer < ActionMailer::Base default from: 'sender@mail.com', to: 'recipient@mail.com' # ==== Parameter # * +user+ # * +controller+ # * +request+ # * +exception+ def system_error(user, controller, request, exception) @user = user @controller = controller @request = request @exception = exception subject = "[ApplicationName #{Rails.env}][error] System Error has occurred!!" mail(subject: subject) end end |
Simply, pass arguments to instance variables. Show them in mail body message.
Sender is recipient@email.com
as default. It can be changed for each mail. In practice, using google group as mailing list is good, if your development team uses google services. Manage recipients in the database is also good.
Mail Body Message
By the command executed in above, app/views/system_mailer/system_error.text.erb
is created. Now, edit it. If you prefer html format, use file ends like .html.erb
. As for me, I deleted html format view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
============================================================ base info ============================================================ [user id] <%=@user.nil? ? '(without access token or logging in)' : @user.id %> [time] <%=Time.now%> [host] <%=`hostname`.strip%> [source] <%=@controller.controller_name%>#<%=@controller.action_name%> ============================================================ request info ============================================================ [URL] <%=@request.original_url%> [method] <%=@request.request_method%> [header] <%@request.headers.each{|k,v|%> <%=k%> : <%=v.to_s%> <%}%> [parameter] <%=@request.parameters.inspect%> [referrer] <%=@request.referrer.blank? ? '(no referrer)' : @request.referrer%> ============================================================ exception info ============================================================ [class name] <%=@exception.class%> [message] <%=@exception.message%> [backtrace] <%@exception.backtrace.each{|trace|%> <%=trace%> <%}%> |
I used force_encoding
function on outputting header data. Without force_encoding
, if Japanese letters are included in request parameter, the following error will be raised.
ActionView::Template::Error (incompatible character encodings: ASCII-8BIT and UTF-8)
We should be prevent from heavy view, so I should change Time.now
and
should be changed.hostname
.strip
In my case, I used above code little changed for logging addition to mail notification.