In our Rails application we use Airbrake connected to a hosted Errbit server.
We use rescue and rescue_from in a lot of places where we want to handle any exceptions in a particular way and then we log the exception ourselves before returning a response.
Some examples we have in our ApplicationController:
rescue_from CanCan::AccessDenied do |e|
Rails.logger.error "CanCan exception: #{e.message}"
render 'errors/401', status: 401
end
rescue_from ActionController::InvalidAuthenticityToken do |e|
Rails.logger.error "Authenticity exception: #{e.message}"
render 'errors/csrf', status: 400
end
And then we also have some in individual methods such as an API:
def try_request
Response.new(yield)
rescue RestClient::Unauthorized,
RestClient::ExceptionWithResponse,
RestClient::InternalServerError,
RestClient::BadRequest => e
Rails.logger.error "API exception: #{e.message}"
Response.new(e.response)
end
However by using rescue we noticed that Errbit was no longer picking up our execeptions because we were capturing them... makes sense... but we want to still see these in our Errbit reports!
You can manually notify Airbrake with:
Airbrake.notify(e)
But we were hoping to avoid having to put this code in every single rescue block.
Is it possible to have a higher-level statement that can notify Airbrake automatically when manually logging errors? Perhaps middleware that is called when Rails.logger.error is called?
rescue is ruby clause, you cannot override it directly (and even if you could - it would not be a great idea)
In all your examples there's Rails.logger.error, you can make yourself a helper like:
module MyErrorNotifier
def self.notify(message, exception, params = {}, &block)
Rails.logger.error("#{message}: #{exception.message}")
Airbrake.notify(exception, params, &block)
end
end
and instead of logger call it:
def try_request
...
rescue e
MyErrorNotifier.notify("Foo exception happened", e)
Response.new(e.response)
end
Related
I have problem with handling exceptions. I know how to do it, but I'm not sure what's the correct place to rescue them. For example:
class ExampleService
def call
...
raise ExampleServiceError, 'ExampleService message'
end
end
class SecondExampleService
def call
raise SecondExampleServiceError if something
ExampleService.call
rescue ExampleService::ExampleServiceError => e ---> should I rescue it here?
raise SecondExampleServiceError, e.message
end
end
Class ExampleController
def update
SecondExampleService.call
rescue ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError => e
render json: { error: e.message }
end
end
As you can see in example I have two Services. ExampleService raises his own exception. SecondExampleService call the ExampleService, can raise exception and is used in ExampleController. Where should I rescue ExampleServiceError? In ExampleController or SecondExampleService? If in ExampleController, what about when we use such a Service in multiple controllers (rescue code will be repeated many times)?
When the controller only calls SecondExampleService directly and doesn't know anything about ExampleService, then it should not rescue from ExampleServiceError.
Only SecondExampleService knows that ExampleService is used internally, and therefore SecondExampleService should rescue from ExampleServiceError and translate it into a SecondExampleServiceError.
That is my interpretation of the law of demeter.
I built an error handler that, when there's an error in any controller method in production, reroutes the user to an error page and sends me, the developer, a notification email. This works, but I want the normal error screen to appear when I'm in development. I assumed the code to produce this was just raise e, but instead in development I'm now getting the default production error page (the one that says "We're sorry, but something went wrong".), instead of the detailed error message and trace that used to appear.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error
#error handler
def handle_error(e)
if Rails.env.production?
#code to send email and redirect to error page
else
raise e
end
end
end
I also tried the following:
raise
raise StandardError.new e
raise e, e.message
raise e.message
and if I run any of those in a binding.pry console, they produce the error message I'm looking for, but the error page still just says "We're sorry, but something went wrong."
Anyone know how I can just show the default development error page?
UPDATE
This is insane...so the code to display an error normally should definitely work, but something somewhere is preventing that. If I change config.consider_all_requests_local = true on production, the errors show up on production, but then even if I copy and paste my config/environments/production.rb file into my config/environments/development.rb, the errors still don't show on development. If I enter a pry console, request.local? returns "0", signifying true, and Rails.env returns "development". I have no idea what is going on.
UPDATE 2
Apparently I'm not supposed to be rescuing exceptions on development, but even if I delete every bit of custom error handling code so my Application Controller is just empty, my errors still don't show on development. Further, I have a different app with the same exact error handling code, and for that the errors do show.
Search your code for consider_all_requests_local, it's this configuration that show the full error log.
It must be set as true on your development.rb config file. It's either missing from your configs, or other config is overwriting it
This is not "insane", this is completely expected behavior. You cannot raise from within a rescue_from handler. That would cause an infinite loop.
You also cannot rescue_from StandardError as stated specifically in the documentation:
Using rescue_from with Exception or StandardError would cause serious side-effects as it prevents Rails from handling exceptions properly. As such, it is not recommended to do so unless there is a strong reason.
Instead of conditionally handling the exception inside your rescue_fromhandler, you should conditionally bind the handler, and pick a more specific exception class to handle.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error if Rails.env.production?
#error handler
def handle_error(e)
#code to send email and redirect to error page
end
end
create ErrorsController like below
class ErrorsController < ApplicationController
skip_before_action :login
def not_found
respond_to do |format|
format.html { render status: 404 }
format.json { render json: { error: "Not found" }, status: 404 }
end
end
end
According to the family tree of Exception. SyntaxError is child to ScriptError
I wish to handle Syntax and/or ScriptError in my rails application.
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SignalException
Interrupt
StandardError
ArgumentError
IOError
EOFError
IndexError
StopIteration
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
SecurityError
SystemCallError
SystemStackError
ThreadError
TypeError
ZeroDivisionError
SystemExit
fatal
I did:
rescue_from ScriptError, :with => :notify_on_landing_page
but didn't worked.
Error raised on screen :
SyntaxError in Bc::Muse::User::ProfileController#show
I have created an explicit syntax error, it should gracefully rescue it and do the things I want to.
Unfortunately, I don't think it works that way.
Rescuing from exceptions with rescue_from works only after creating a controller instance during request processing (see source code here and here). Your SyntaxError is probably raised much earlier - during autoloading of the given controller and dependent classes / modules. So unless you were trying to rescue from a syntax error of code being loaded during a controller action execution, you are out of luck, I'm afraid.
Test: when you explicitly load a file with a syntax error in a controller action and the rescue_from will work as expected:
class MyController < ApplicationController
rescue_from(::SyntaxError) { Rails.logger.error "SYNTAX ERROR!" }
def index
load "#{Rails.root}/test.rb"
end
end
If you save a test.rb file in the rails root and add a deliberate syntax error in it, you will see the exception is handled correctly by rescue_from and the error message will be present in the log file.
On the other hand, if you look at the full stack trace of your SyntaxError, you will probably see that it does not even reach the ActionController methods for request processing.
Thanks!
I added a middleware to achieve this.
class RescueSyntaxError
def initialize(app)
#app = app
end
def call(env)
#app.call(env)
rescue SyntaxError => error
request = Rack::Request.new(env)
session = request.env['rack.session']
params = request.params
if session.try(:[], :user_object)
##Do validation stuff
...
[302, {'Location' => '/'}, []]
end
end
end
I am attempting to rescue RuntimeErrors from within my controller using rescue_from, log it as a HealthNotice, and then redirect the user with a flash message, but I'm finding that in certain circumstances, the rescue_from method is being run twice (I end up with 2 duplicate HealthNotices). If the exception is rasied from a function called in a method (such as the test method below), it only creates one, but if I raise the exception directly within the method (such as test2), the rescue function is running twice (although strangely I don't get a double render error -- ??)
class Portal::BluerimController < PortalController
rescue_from RuntimeError, with: :error_handler
def error_handler(e)
hn_long_message = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}\n\nPARAMS:\n#{params}"
HealthNotice.create(name: "API Exception Handled", severity: "WARNING", short_message: e.message, long_message: hn_long_message)
flash[:notice] = e.message
redirect_to :action => :index and return
end
def test
Sotal.new.test # a method that raises a RuntimeError -- only 1 HealthNotice
end
def test2
raise "an error" # results in duplicate HealthNotice
end
end
Any suggestions on what I'm doing wrong? Thanks!
I have a number of controllers in my Ruby on Rails apps with a rescue handler at the end of the action that basically catches any unhandled errors and returns some kind of "user friendly" error. However, when I'm doing rake test I'd like to have those default rescue handlers disabled so I can see the full error & stack trace. Is there any automated way to do this?
Update to clarify: I have an action like this:
def foo
# do some stuff...
rescue
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Now when I functional test this, if the exception is raised then I'm going to get just a little bit of info about the exception, but what I'd like is for it to act as though there's no rescue handler there, so I get the full debug information.
Update: SOLUTION
I did this:
rescue:
raise unless Rails.env.production?
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Not quite automated, but how modifying your code to re-throw exceptions whenever called within a test?
Perhaps something like this:
def foo
# do some stuff...
rescue
raise if ENV["RAILS_ENV"] == "test"
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Have you looked at using the assert_raise( exception1, exception2, ... ) { block } call and then printing the exception from the block?
Which method are you using? There are two rescue methods in ActionController.
I have this in my base controller:
def rescue_action_in_public(exception)
response_code = response_code_for_rescue(exception)
status = interpret_status(response_code)
respond_to do |format|
format.html { render_optional_error_file response_code}
format.js { render :update, :status => status do |page| page.redirect_to(:url => error_page_url(status)) end}
end
end
This only displays custom errors in production mode.
I think the easiest thing to do is verify that the correct render was called-- or whatever was different from the regular, non-exceptional case.
You shouldn't need to disable your rescue block. Use the assert_raise method (as suggested by Scott), and in the block, call the method that you expect an exception from.
For example:
def test_throws_exception
assert_raise Exception do
raise_if_true(true)
end
end