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
Related
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
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
Why doesn't this work?? I expect the rescue block to be executed if an ActionView::TemplateError is raised. But that isn't happening for some reason...
The following is a controller action in a rails 4 app.
def categorized
#ActionView::TemplateError raising code
rescue ActionView::TemplateError
binding.pry
end
how about if move exception in application_controller.rb:
rescue_from ActionView::TemplateError do | exception |
binding.pry
end
Try with
rescue => e
bindig.pry
After you that you can check error class with e.class, to make sure you are catching good exception.
The Grape documentation says that:
The rescue_from block must return a Rack::Response object, call error! or re-raise an exception.
but if we use the rescue_from method only to log things and we want to keep the original HTTP answer what should we return ?
If I rethrow the catched exception, it does not keep the original behavior.
For example, some of my tests generate 400 responses but with:
rescue_from :all do |e|
Rails.logger.error(e)
raise e
end
They catch 500 responses.
The better solution I found to reproduce the original behavior is this code:
rescue_from :all do |e|
Rails.logger.error(e)
if e.respond_to?(:status)
error!(e.to_json, e.status, e.headers, e.backtrace)
else
error! "Internal Server Error"
end
end
I should check if the catched exception responds to status because it could be a RuntimeError for example, and RuntimeError does not answer to status.
Am I doing this right ? Or there is a better solution to do that ?
This is answered in https://github.com/ruby-grape/grape/pull/939, pointed from https://github.com/ruby-grape/grape/issues/1300.
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!