When an error occurs, the Lucky::ErrorHandler calls the auto-generated
Errors::Show action in src/actions/errors/show.cr.
The Errors::Show action has 3 key methods:
render(error)default_render(error)report(error)First, the Errors::Show action will try to find a matching render
method. The render methods take the error as an argument and uses method
overloading
to find a match. If one matches, the error will be rendered.
For example, if MyCustomError is raised, and there is a method for
handling it, that method will be used and default_render will not be
called:
def render(error : MyCustomError)
error_html message: "Super Custom", status: 418
end
error_html is an automatically generated method on Errors::Show that
shows an HTML page. You can customize it however you want. You can learn
more about how to customize how errors are displayed
later in the guide.
If no render method matches then the default_render method will be
used. This method will send a 500 HTTP status code and either an HTML
page or a JSON response depending on the client’s desired format. If your
app is using API mode, it will only send JSON and not an HTML page.
You can learn more about how to customize how errors are displayed later in the guide.
This method handles reporting the error. We’ll talk about this more in the section on reporting errors
When using a browser with Lucky in development mode, Lucky uses the ExceptionPage shard to display a helpful page with your stack trace, and exception message.
When using JSON, Lucky will render errors as JSON whether in development or production.
Sometimes in development you want to see the page your users will see instead of the debug page.
To do so, change the the settings.show_debug_output option to false:
# config/error_handler.cr
Lucky::ErrorHandler.configure do |settings|
settings.show_debug_output = false
end
Remember to change it back once you’re done so you can see the debug page.
Let’s say you have an error class MyCustomError in your app. When this
error is raised, you want to show a custom error to your users. Open up the
Errors::Show in src/actions/errors/show.cr, and add your render
method like this.
def render(error : MyCustomError)
if html?
error_html message: "Custom error message.", status: 418
else
error_json message: "Custom error", status: 418
end
end
If there is no render for the exception, it will fallback to the
default one that is generated with every Lucky project: default_render(error : Exception). You can customize that method in the same way by changing
the message or status codes:
def default_render(error : Exception)
error_json "Something went very very wrong", status: 500
end
You can customize an error for just one format if you’d like:
def render(error : ThisIsOnlyImportantForJsonError)
if json?
error_json "Something for JSON clients", status: 418
end
end
If the client wants JSON back, it will get this error message, otherwise
the method will return nil and Lucky will fall back to using the
default_render method.
Lucky generates error_json and error_html methods in Errors::Show.
These methods and the pages/serializers they call can be customized.
For example, error_html renders Errors::ShowPage. You can change that
page’s styles and content in src/pages/errors/show_page.cr.
Lucky will also handle a few errors out of the box. For example,
Lucky::RouteNotFoundError will return a 404:
def render(error : Lucky::RouteNotFoundError)
if html?
error_html "Sorry, we couldn't find that page.", status: 404
else
error_json "Not found", status: 404
end
end
If you open src/actions/errors/show.cr, you’ll see the other errors
that Lucky handles by default.
One of special note is the Lucky::RenderableError. We’ll talk about
these more in the section on renderable
errors
You may need to use custom errors for your control flow like manually rendering a 404 page when a user shouldn’t see certain pages, for example.
Since Lucky will pass all exceptions to the Errors::Show action for you,
you can raise specific errors to display the error page you want.
get "/profiles/:slug" do
profile = ProfileQuery.find_by_id_or_slug(slug)
if current_user.is_allowed_to_view?(profile)
# Return a 202 with the appropriate page
html ShowPage, profile: profile
else
# raising this error will render the 404 page for you
raise Lucky::RouteNotFoundError.new(context)
end
end
For rending non 200 status pages without raising errors, see rendering HTML with non 200 status
In general this should be a last resort or for libraries that want to provide default behavior for errors. Usually you should use
rendermethods inErrors::Showbecause it is more customizable and simpler to work with.
Lucky comes with a Lucky::RenderableError module that can be included in
errors so that Lucky knows what the status code and message should be.
Errors with Lucky::RenderableError must have a renderable_status and
renderable_message method defined.
By default, Lucky::RenderableErrors are handled with the render(error : Lucky::RenderableError) method included in all new Lucky projects.
It looks something like this:
def render(error : Lucky::RenderableError)
if html?
error_html DEFAULT_MESSAGE, status: error.renderable_status
else
error_json error.renderable_message, status: error.renderable_status
end
end
If you want to make it so your error is rendered with this method, you can do this:
# Define your custom exception
class NotAuthorizedError < Exception
include Lucky::RenderableError
def renderable_status
403
end
def renderable_message
"Not authorized"
end
end
When NotAuthorizedError is raised, Lucky will use the defined status
code and message, unless you have a render method for the error
(render(error : NotAuthorizedError)).
In your src/actions/errors/show.cr file, there is a report method.
By default this method is empty, but you can change it to report the
error however you want. You can send an email, send the error to one or
more services, or anything else you want.
Let’s use the Raven shard to send an error report to Sentry:
# src/actions/errors/show.cr
def report(error : Exception)
Raven.capture(error)
end
This will send the error report to Sentry. See the Raven README to learn more about installing and how to customize error reporting with Sentry.
You can use method overloading to report some errors differently than
others. For example, let’s say we have a SuperScaryError that we want
to report by sending a text to the CEO. We can add a report method that
handles that error:
def report(error : SuperScaryError)
NotifyTheBoss.run!
end
Now SuperScaryError will be handled by report(error : SuperScaryError), and all other errors will be handled by the regular
report(error : Exception).
Some errors don’t need to be reported. Errors::Show has a dont_report
macro that accepts an array of classes that should not be reported. By
default Lucky does not report Lucky::RouteNotFoundError, but you can
add any errors there that you don’t want reported.
dont_report [
Lucky::RouteNotFoundError,
MyCustomError
]