I'm running a Rails 4.0.0.rc application using New Relic for availability / exception monitoring. I modified application.rb with this snippet to enable dynamic exception pages:
config.exceptions_app = self.routes
However, I no longer see 404, 422 or 500 exceptions in New Relic. Any idea how I get them back?
Edit:
Note: this is what the controller handling the status looks like:
class ErrorsController < ApplicationController
# GET /404
def missing
render status: 404
end
# GET /422
def unprocessable
render status: 422
end
# GET /500
def exception
render status: 500
end
end
It sounds like you want to call NewRelic::Agent.notice_error manually.
You can reconstruct the request object from the Rack env and build an exception however you would like.
Something like this:
request = Rack::Request(env)
options = {
:uri => request.url,
:referrer => request.referrer,
:request_params => request.params
}
NewRelic::Agent.notice_error(your_custom_exception, options)
Note that the request parameters will be transmitted as is so be careful to filter anything sensitive.
Sources:
I work for New Relic as Ruby Agent Engineer
Documentation for NoticedError: http://rubydoc.info/gems/newrelic_rpm/frames
You will have to set the html status code to the correct value in your errors controller. If you for example have something like this:
class ErrorsController < ApplicationController
# 404
def not_found
render "not_found", status: 404
end
end
Otherwise will rails render the error page with a 200 status code, and new relic will not pick it up as an error.
Related
I have a webhook sent to a page on my app, and I am catching SOAP errors from processing the data, but I want to send to the webhook a 200 status. Is this possible in the controller?
controller
def example_method
...
rescue Savon::SOAPFault => error
...
# Respond to the HTTP POST request by giving the 'all clear'
head 200
raise
end
First off don't place soap calls directly into your controller - instead create a client class or service object that handles it.
Your controller should only really know that it calls some kind of service and that it may fail or succeed - if you are catching library specific errors in your controller you are on your way down the rabbit hole.
So how do you rescue exceptions in your controller?
You can rescue them inline:
class SomethingController
def do_something
begin
# Something.this_may_blow_up!
rescue SomeError => error
head :ok and return
end
end
end
The key here is and return which halts the controller flow and prevents double render errors.
The other method is by using rescue_from.
class ThingsController
rescue_from ActiveRecord::RecordNotFound do
render :not_found and return
end
def show
#thing = Thing.find(params[:id])
end
end
The above is an example of how you can create a custom 404 page per controller. What happens is that when the action on the controller is invoked it is wrapped in a begin..rescue block that looks for rescue_from handlers in the controller.
So I want to render an error page and pass in an error message inside a rescue_from section in a Rails controller. It looks like this currently:
rescue_from ActiveSupport::MessageVerifier::InvalidSignature do
render json: { errors: I18n.t(:invalid_token, scope: :errors) }, status: :bad_request
end
But we no longer want to render json. We want to render a rails template. How do we do this but also passing in the errors hash? Can this be done inside a rescue_from?
A common way of responding when there are errors is by using an exceptions_app for your application, first create a controller to serve the error responses:
class ErrorsController < ApplicationController
def not_found
#details = "foo bar, something that happened in this request"
render status: 404
end
def internal_server_error
render status: 500
end
end
You can assign instance vars in the controller, like ordinary controller actions, here you can build a message to the user or handle it however you like.
Then you can add some routes in your router:
match "/404", to: "errors#not_found", via: :all
match "/500", to "errors#internal_server_error", via: :all
In you application.rb file, add:
config.exceptions_app = self.routes
And last be sure to remove the 404.html and 500.html in your public dir if they are present.
NOTE:
If you want to see these pages locally during development you'll need to modify your config/environments/development.rb:
config.consider_all_requests_local = false
So I handle exceptions with an error controller to display dynamic content to my users in production. I have it in my route file to do:
# Errors
%w( 404 422 500 ).each do |code|
get code, :to => "errors#show", :code => code
end
The only problem is now that I'm routing on errors such as that I lose information in my controller when I want to notify Airbrake. How can I maintain the exception information and send it to Airbrake on a 500? Right now all I get is the env that was occurring at the time of the exception which is less helpful for debugging purposes.
class ErrorsController < ApplicationController
def show
notify_airbrake(env)
render status_code.to_s, :status => status_code
end
protected
def status_code
params[:code] || 500
end
end
Are you handling an error by redirecting to a URL like http://your.site/500? That will be just an HTTP request like any other, losing the exception context you're after. Instead, you probably want to be using ActionController's Rescue functionality. It would look like this:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :render_error
private
def render_error(error)
notify_airbrake(error)
render text: 500, status: 500
end
end
You can add multiple rescue_from declarations to handle different kinds of error, like the ActiveRecord::RecordNotFound from the Rails guide's example.
I am using rails-api to build an API with no web interface. When I get errors in development, I'd love to see just the error message and stacktrace in plain text without all of the HTML wrapping. How do I override the global exception handling so it renders the stacktrace in development mode in plain text/JSON, and a generic error message in production?
I would suggest that including the stack trace in production code is probably not a good idea from a security stand point.
Here is how I would do it:
render :json => {message:exception.message, stack_trace: exception.stacktrace}
I hope this helps.
After Sam's clarification I can add:
In your base controller for your API (probably ApplicationController):
class ApplicationController < ActionController::Base
...
rescue_from Exception do |exception|
error = {message:exception.message}
error[:stack_trace] = exception.stacktrace if Rails.env.development?
render :json => error
end
...
end
Caveat: You may not want to rescue from every single exception in this way but this is how you'd do it if you did.
Some improvements over #donleyp answer to get a clean trace in Rails 3.2 and output generic error info in production:
class ApplicationController < ActionController::API
...
rescue_from Exception do |exception|
if Rails.env.development?
error = {message:exception.message}
error[:application_trace] = Rails.backtrace_cleaner.clean(exception.backtrace)
error[:full_trace] = exception.backtrace
render :json => error
else
render :text => "Internal server error.", :status => 500
end
end
...
end
I am overriding Devise's failure response so that I can set a 401 status code. However, when the user fails to sign in, they are redirected to a page with a "you are being redirected" link. If I remove this :status => 401 from the redirect it works fine.
class CustomFailure < Devise::FailureApp
def redirect_url
new_user_session_url(:subdomain => 'secure')
end
def respond
if http_auth?
http_auth
else
store_location!
flash[:alert] = i18n_message unless flash[:notice]
redirect_to redirect_url, :status => 401
end
end
end
edit
Alternatively I would like to display the flash message and remain on the same page but adding this line of code:
render :text => "unauthorized", :status => 401
causes ruby to complain:
undefined method `render' for #<CustomFailure:0x00000103367f28>
What's happening here?
Proper HTTP statuses for a redirection are in the 30x form (301 and 302 being the most frequently used). By default, the redirect_to helper sets a 302 status header on the HTTP response. If you override that and set that to a 401, your web browser will assume that the response is a regular web page and will render the response body --which, in a redirection, is the boilerplate text "You are being redirected".
As said by #pantulis the browser will display this standard message if the response code is not a 3xx
To workaround this you can perform a javascript redirect:
# example with status 500:
render text: "<script>window.location = '#{url}';</script>", status: 500
This is off-course valid only if you are sure that all your users are using javascript. If your application can be browsed by users that may have disabled javascript you should also include a noscript tag and fallback in the standard "You are being redirected" message
I was actually running into this problem on our QA server, but not locally. It turned out that our memcache was intercepting the message and rendering it as a 200, and causing this message to appear. This was due indirectly to our memcache settings which didn't expect a re-direct from a GET.
From:
$document_root/cache/$uri.html /cache/$uri /cache/$uri.html $uri #memcached
To:
$document_root/cache/$uri.html /cache/$uri /cache/$uri.html $uri #rails
When I have this problem what I have done in the past is something like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
after_filter :check_page_content
...
private
def check_page_content
if response.body.include? "You are being"
html_doc = Nokogiri::HTML(response.body)
uri = html_doc.css('a').map { |link| link['href'] }.first
response.body = "<script>
window.location.replace('#{uri}');
</script>"
end
end
end
What I am doing is checking to see if the page content is "You are being". if this is true I know I am not where I want to be. and I just update the page to where I really want to be with some help of Javascript.
I know its not the most elegant solution but it really does help
Happy Hacking