Merge pull request #3669 from ebonnecab/error-docs
Error Docs Revision/Consolidation
This commit is contained in:
commit
36e6fc8ab8
5 changed files with 408 additions and 441 deletions
|
|
@ -242,8 +242,9 @@ you can use relative redirects by prefixing the endpoint with a dot only::
|
|||
This will link to ``admin.index`` for instance in case the current request
|
||||
was dispatched to any other admin blueprint endpoint.
|
||||
|
||||
Error Handlers
|
||||
--------------
|
||||
|
||||
Blueprint Error Handlers
|
||||
------------------------
|
||||
|
||||
Blueprints support the ``errorhandler`` decorator just like the :class:`Flask`
|
||||
application object, so it is easy to make Blueprint-specific custom error
|
||||
|
|
|
|||
97
docs/debugging.rst
Normal file
97
docs/debugging.rst
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
Debugging Application Errors
|
||||
============================
|
||||
|
||||
|
||||
In Production
|
||||
-------------
|
||||
|
||||
**Do not run the development server, or enable the built-in debugger, in
|
||||
a production environment.** The debugger allows executing arbitrary
|
||||
Python code from the browser. It's protected by a pin, but that should
|
||||
not be relied on for security.
|
||||
|
||||
Use an error logging tool, such as Sentry, as described in
|
||||
:ref:`error-logging-tools`, or enable logging and notifications as
|
||||
described in :doc:`/logging`.
|
||||
|
||||
If you have access to the server, you could add some code to start an
|
||||
external debugger if ``request.remote_addr`` matches your IP. Some IDE
|
||||
debuggers also have a remote mode so breakpoints on the server can be
|
||||
interacted with locally. Only enable a debugger temporarily.
|
||||
|
||||
|
||||
The Built-In Debugger
|
||||
---------------------
|
||||
|
||||
The built-in Werkzeug development server provides a debugger which shows
|
||||
an interactive traceback in the browser when an unhandled error occurs
|
||||
during a request. This debugger should only be used during development.
|
||||
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of debugger in action
|
||||
|
||||
.. warning::
|
||||
|
||||
The debugger allows executing arbitrary Python code from the
|
||||
browser. It is protected by a pin, but still represents a major
|
||||
security risk. Do not run the development server or debugger in a
|
||||
production environment.
|
||||
|
||||
To enable the debugger, run the development server with the
|
||||
``FLASK_ENV`` environment variable set to ``development``. This puts
|
||||
Flask in debug mode, which changes how it handles some errors, and
|
||||
enables the debugger and reloader.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_ENV=development
|
||||
$ flask run
|
||||
|
||||
``FLASK_ENV`` can only be set as an environment variable. When running
|
||||
from Python code, passing ``debug=True`` enables debug mode, which is
|
||||
mostly equivalent. Debug mode can be controled separately from
|
||||
``FLASK_ENV`` with the ``FLASK_DEBUG`` environment variable as well.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(debug=True)
|
||||
|
||||
:doc:`/server` and :doc:`/cli` have more information about running the
|
||||
debugger, debug mode, and development mode. More information about the
|
||||
debugger can be found in the `Werkzeug documentation
|
||||
<https://werkzeug.palletsprojects.com/debug/>`__.
|
||||
|
||||
|
||||
External Debuggers
|
||||
------------------
|
||||
|
||||
External debuggers, such as those provided by IDEs, can offer a more
|
||||
powerful debugging experience than the built-in debugger. They can also
|
||||
be used to step through code during a request before an error is raised,
|
||||
or if no error is raised. Some even have a remote mode so you can debug
|
||||
code running on another machine.
|
||||
|
||||
When using an external debugger, the app should still be in debug mode,
|
||||
but it can be useful to disable the built-in debugger and reloader,
|
||||
which can interfere.
|
||||
|
||||
When running from the command line:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_ENV=development
|
||||
$ flask run --no-debugger --no-reload
|
||||
|
||||
When running from Python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.run(debug=True, use_debugger=False, use_reloader=False)
|
||||
|
||||
Disabling these isn't required, an external debugger will continue to
|
||||
work with the following caveats. If the built-in debugger is not
|
||||
disabled, it will catch unhandled exceptions before the external
|
||||
debugger can. If the reloader is not disabled, it could cause an
|
||||
unexpected reload if code changes during debugging.
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
Handling Application Errors
|
||||
===========================
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
Applications fail, servers fail. Sooner or later you will see an exception
|
||||
in production. Even if your code is 100% correct, you will still see
|
||||
exceptions from time to time. Why? Because everything else involved will
|
||||
fail. Here are some situations where perfectly fine code can lead to server
|
||||
Applications fail, servers fail. Sooner or later you will see an exception
|
||||
in production. Even if your code is 100% correct, you will still see
|
||||
exceptions from time to time. Why? Because everything else involved will
|
||||
fail. Here are some situations where perfectly fine code can lead to server
|
||||
errors:
|
||||
|
||||
- the client terminated the request early and the application was still
|
||||
|
|
@ -18,8 +16,8 @@ errors:
|
|||
- a programming error in a library you are using
|
||||
- network connection of the server to another system failed
|
||||
|
||||
And that's just a small sample of issues you could be facing. So how do we
|
||||
deal with that sort of problem? By default if your application runs in
|
||||
And that's just a small sample of issues you could be facing. So how do we
|
||||
deal with that sort of problem? By default if your application runs in
|
||||
production mode, and an exception is raised Flask will display a very simple
|
||||
page for you and log the exception to the :attr:`~flask.Flask.logger`.
|
||||
|
||||
|
|
@ -27,71 +25,140 @@ But there is more you can do, and we will cover some better setups to deal
|
|||
with errors including custom exceptions and 3rd party tools.
|
||||
|
||||
|
||||
.. _common-error-codes:
|
||||
.. _error-logging-tools:
|
||||
|
||||
Common Error Codes
|
||||
``````````````````
|
||||
Error Logging Tools
|
||||
-------------------
|
||||
|
||||
The following error codes are some that are often displayed to the user,
|
||||
even if the application behaves correctly:
|
||||
Sending error mails, even if just for critical ones, can become
|
||||
overwhelming if enough users are hitting the error and log files are
|
||||
typically never looked at. This is why we recommend using `Sentry
|
||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
||||
available as an Open Source project `on GitHub
|
||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
||||
aggregates duplicate errors, captures the full stack trace and local
|
||||
variables for debugging, and sends you mails based on new errors or
|
||||
frequency thresholds.
|
||||
|
||||
*400 Bad Request*
|
||||
When the server will not process the request due to something that
|
||||
the server perceives to be a client error. Such as malformed request
|
||||
syntax, missing query parameters, etc.
|
||||
To use Sentry you need to install the ``sentry-sdk`` client with extra
|
||||
``flask`` dependencies.
|
||||
|
||||
*403 Forbidden*
|
||||
If you have some kind of access control on your website, you will have
|
||||
to send a 403 code for disallowed resources. So make sure the user
|
||||
is not lost when they try to access a forbidden resource.
|
||||
.. code-block:: text
|
||||
|
||||
*404 Not Found*
|
||||
The good old "chap, you made a mistake typing that URL" message. So
|
||||
common that even novices to the internet know that 404 means: damn,
|
||||
the thing I was looking for is not there. It's a very good idea to
|
||||
make sure there is actually something useful on a 404 page, at least a
|
||||
link back to the index.
|
||||
$ pip install sentry-sdk[flask]
|
||||
|
||||
*410 Gone*
|
||||
Did you know that there the "404 Not Found" has a brother named "410
|
||||
Gone"? Few people actually implement that, but the idea is that
|
||||
resources that previously existed and got deleted answer with 410
|
||||
instead of 404. If you are not deleting documents permanently from
|
||||
the database but just mark them as deleted, do the user a favour and
|
||||
use the 410 code instead and display a message that what they were
|
||||
looking for was deleted for all eternity.
|
||||
And then add this to your Flask app:
|
||||
|
||||
*500 Internal Server Error*
|
||||
Usually happens on programming errors or if the server is overloaded.
|
||||
A terribly good idea is to have a nice page there, because your
|
||||
application *will* fail sooner or later.
|
||||
.. code-block:: python
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
|
||||
sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])
|
||||
|
||||
The ``YOUR_DSN_HERE`` value needs to be replaced with the DSN value you
|
||||
get from your Sentry installation.
|
||||
|
||||
After installation, failures leading to an Internal Server Error
|
||||
are automatically reported to Sentry and from there you can
|
||||
receive error notifications.
|
||||
|
||||
See also:
|
||||
|
||||
- Sentry also supports catching errors from a worker queue
|
||||
(RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs
|
||||
<https://docs.sentry.io/platforms/python/>`__ for more information.
|
||||
- `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`__
|
||||
- `Flask-specific documentation <https://docs.sentry.io/platforms/python/flask/>`__
|
||||
|
||||
|
||||
Error Handlers
|
||||
--------------
|
||||
|
||||
Default Error Handling
|
||||
``````````````````````
|
||||
When an error occurs in Flask, an appropriate `HTTP status code
|
||||
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`__ will be
|
||||
returned. 400-499 indicate errors with the client's request data, or
|
||||
about the data requested. 500-599 indicate errors with the server or
|
||||
application itself.
|
||||
|
||||
You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
An error handler is a function that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`.
|
||||
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler.
|
||||
|
||||
|
||||
Registering
|
||||
```````````
|
||||
|
||||
Register handlers by decorating a function with
|
||||
:meth:`~flask.Flask.errorhandler`. Or use
|
||||
:meth:`~flask.Flask.register_error_handler` to register the function later.
|
||||
Remember to set the error code when returning the response.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||
def handle_bad_request(e):
|
||||
return 'bad request!', 400
|
||||
|
||||
# or, without the decorator
|
||||
app.register_error_handler(400, handle_bad_request)
|
||||
|
||||
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
||||
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
||||
when registering handlers. (``BadRequest.code == 400``)
|
||||
|
||||
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||
by Werkzeug. Instead, define a subclass of
|
||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||
register and raise that exception class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||
code = 507
|
||||
description = 'Not enough storage space.'
|
||||
|
||||
app.register_error_handler(InsufficientStorage, handle_507)
|
||||
|
||||
raise InsufficientStorage()
|
||||
|
||||
Handlers can be registered for any exception class, not just
|
||||
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
|
||||
codes. Handlers can be registered for a specific class, or for all subclasses
|
||||
of a parent class.
|
||||
|
||||
|
||||
Handling
|
||||
````````
|
||||
|
||||
When building a Flask application you *will* run into exceptions. If some part
|
||||
of your code breaks while handling a request (and you have no error handlers
|
||||
registered) an "500 Internal Server Error"
|
||||
registered), a "500 Internal Server Error"
|
||||
(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default.
|
||||
Similarly, if a request is sent to an unregistered route a "404 Not Found"
|
||||
(:exc:`~werkzeug.exceptions.NotFound`) error will occur. If a route receives an
|
||||
unallowed request method a "405 Method Not Allowed"
|
||||
Similarly, "404 Not Found"
|
||||
(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route.
|
||||
If a route receives an unallowed request method, a "405 Method Not Allowed"
|
||||
(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all
|
||||
subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by
|
||||
default in Flask.
|
||||
|
||||
Flask gives you to the ability to raise any HTTP exception registered by
|
||||
werkzeug. However, as the default HTTP exceptions return simple exception
|
||||
pages, Flask also offers the opportunity to customise these HTTP exceptions via
|
||||
custom error handlers as well as to add exception handlers for builtin and
|
||||
custom exceptions.
|
||||
Werkzeug. However, the default HTTP exceptions return simple exception
|
||||
pages. You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
When an exception is caught by Flask while handling a request, it is first
|
||||
looked up by code. If no handler is registered for the code, it is looked up
|
||||
by its class hierarchy; the most specific handler is chosen. If no handler is
|
||||
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
When Flask catches an exception while handling a request, it is first looked up by code.
|
||||
If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen.
|
||||
If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
generic message about their code, while other exceptions are converted to a
|
||||
generic "500 Internal Server Error".
|
||||
|
||||
|
|
@ -107,21 +174,118 @@ because the 404 occurs at the routing level before the blueprint can be
|
|||
determined.
|
||||
|
||||
|
||||
Generic Exception Handlers
|
||||
``````````````````````````
|
||||
|
||||
.. _handling-errors:
|
||||
It is possible to register error handlers for very generic base classes
|
||||
such as ``HTTPException`` or even ``Exception``. However, be aware that
|
||||
these will catch more than you might expect.
|
||||
|
||||
Handling Errors
|
||||
```````````````
|
||||
For example, an error handler for ``HTTPException`` might be useful for turning
|
||||
the default HTML errors pages into JSON. However, this
|
||||
handler will trigger for things you don't cause directly, such as 404
|
||||
and 405 errors during routing. Be sure to craft your handler carefully
|
||||
so you don't lose information about the HTTP error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import json
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
"""Return JSON instead of HTML for HTTP errors."""
|
||||
# start with the correct headers and status code from the error
|
||||
response = e.get_response()
|
||||
# replace the body with JSON
|
||||
response.data = json.dumps({
|
||||
"code": e.code,
|
||||
"name": e.name,
|
||||
"description": e.description,
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
return response
|
||||
|
||||
An error handler for ``Exception`` might seem useful for changing how
|
||||
all errors, even unhandled ones, are presented to the user. However,
|
||||
this is similar to doing ``except Exception:`` in Python, it will
|
||||
capture *all* otherwise unhandled errors, including all HTTP status
|
||||
codes.
|
||||
|
||||
In most cases it will be safer to register handlers for more
|
||||
specific exceptions. Since ``HTTPException`` instances are valid WSGI
|
||||
responses, you could also pass them through directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
# pass through HTTP errors
|
||||
if isinstance(e, HTTPException):
|
||||
return e
|
||||
|
||||
# now you're handling non-HTTP exceptions only
|
||||
return render_template("500_generic.html", e=e), 500
|
||||
|
||||
Error handlers still respect the exception class hierarchy. If you
|
||||
register handlers for both ``HTTPException`` and ``Exception``, the
|
||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
||||
because it the ``HTTPException`` handler is more specific.
|
||||
|
||||
|
||||
Unhandled Exceptions
|
||||
````````````````````
|
||||
|
||||
When there is no error handler registered for an exception, a 500
|
||||
Internal Server Error will be returned instead. See
|
||||
:meth:`flask.Flask.handle_exception` for information about this
|
||||
behavior.
|
||||
|
||||
If there is an error handler registered for ``InternalServerError``,
|
||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
||||
be passed an instance of ``InternalServerError``, not the original
|
||||
unhandled error.
|
||||
|
||||
The original error is available as ``e.original_exception``.
|
||||
Until Werkzeug 1.0.0, this attribute will only exist during unhandled
|
||||
errors, use ``getattr`` to get access it for compatibility.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.errorhandler(InternalServerError)
|
||||
def handle_500(e):
|
||||
original = getattr(e, "original_exception", None)
|
||||
|
||||
if original is None:
|
||||
# direct 500 error, such as abort(500)
|
||||
return render_template("500.html"), 500
|
||||
|
||||
# wrapped unhandled error
|
||||
return render_template("500_unhandled.html", e=original), 500
|
||||
|
||||
An error handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||
addition to explicit 500 errors. In debug mode, a handler for "500 Internal Server Error" will not be used.
|
||||
Instead, the interactive debugger will be shown.
|
||||
|
||||
|
||||
Custom Error Pages
|
||||
------------------
|
||||
|
||||
Sometimes when building a Flask application, you might want to raise a
|
||||
:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that
|
||||
something is wrong with the request. Fortunately, Flask comes with a handy
|
||||
:func:`~flask.abort` function that aborts a request with a HTTP error from
|
||||
werkzeug as desired.
|
||||
werkzeug as desired. It will also provide a plain black and white error page
|
||||
for you with a basic description, but nothing fancy.
|
||||
|
||||
Consider the code below, we might have a user profile route, but if the user
|
||||
fails to pass a username we raise a "400 Bad Request" and if the user passes a
|
||||
username but we can't find it, we raise a "404 Not Found".
|
||||
Depending on the error code it is less or more likely for the user to
|
||||
actually see such an error.
|
||||
|
||||
Consider the code below, we might have a user profile route, and if the user
|
||||
fails to pass a username we can raise a "400 Bad Request". If the user passes a
|
||||
username and we can't find it, we raise a "404 Not Found".
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -143,133 +307,51 @@ username but we can't find it, we raise a "404 Not Found".
|
|||
|
||||
return render_template("profile.html", user=user)
|
||||
|
||||
|
||||
|
||||
.. _custom-error-handlers:
|
||||
|
||||
Custom error handlers
|
||||
`````````````````````
|
||||
|
||||
The default :exc:`~werkzeug.exceptions.HTTPException` returns a black and white
|
||||
error page with a basic description, but nothing fancy. Considering
|
||||
these errors *will* be thrown during the lifetime of your application, it is
|
||||
highly advisable to customise these exceptions to improve the user experience
|
||||
of your site. This can be done by registering error handlers.
|
||||
|
||||
An error handler is a normal view function that returns a response, but instead
|
||||
of being registered for a route, it is registered for an exception or HTTP
|
||||
status code that would be raised while trying to handle a request.
|
||||
|
||||
It is passed the instance of the error being handled, which is most
|
||||
likely an integer that represents a :exc:`~werkzeug.exceptions.HTTPException`
|
||||
status code. For example 500 (an "Internal Server Error") which maps to
|
||||
:exc:`~werkzeug.exceptions.InternalServerError`.
|
||||
|
||||
It is registered with the :meth:`~flask.Flask.errorhandler`
|
||||
decorator or the :meth:`~flask.Flask.register_error_handler` to register
|
||||
the function later. A handler can be registered for a status code,
|
||||
like 404 or 500, or for an built-in exception class, like KeyError,
|
||||
or a custom exception class that inherits from Exception or its subclasses.
|
||||
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler or a 200 OK HTTP code will be sent instead.
|
||||
Here is another example implementation for a "404 Page Not Found" exception:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import InternalServerError
|
||||
from flask import render_template
|
||||
|
||||
# as a decorator with an int as the exception code
|
||||
@app.errorhandler(500)
|
||||
def handle_internal_server_error(e):
|
||||
# returning 500 with the text sets the error handler's code
|
||||
# make sure to provide the appropriate HTTP status code
|
||||
# otherwise 200 will be returned as default
|
||||
return 'Internal Server Error!', 500
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
# or, as a decorator with the werkzeug exception for internal server error
|
||||
@app.errorhandler(InternalServerError)
|
||||
def handle_internal_server_error(e):
|
||||
# werkzeug exceptions have a code attribute
|
||||
return 'Internal Server Error!', e.code
|
||||
|
||||
# or, without the decorator
|
||||
app.register_error_handler(500, handle_internal_server_error)
|
||||
|
||||
# similarly with a werkzeug exception
|
||||
app.register_error_handler(InternalServerError, handle_internal_server_error)
|
||||
|
||||
|
||||
|
||||
A handler for "500 Internal Server Error" will not be used when running in
|
||||
debug mode. Instead, the interactive debugger will be shown.
|
||||
|
||||
If there is an error handler registered for ``InternalServerError``,
|
||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
||||
be passed an instance of ``InternalServerError``, not the original
|
||||
unhandled error. The original error is available as ``e.original_exception``.
|
||||
Until Werkzeug 1.0.0, this attribute will only exist during unhandled
|
||||
errors, use ``getattr`` to get access it for compatibility.
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.errorhandler(InternalServerError)
|
||||
def handle_500(e):
|
||||
original = getattr(e, "original_exception", None)
|
||||
from flask import Flask, render_template
|
||||
|
||||
if original is None:
|
||||
# direct 500 error, such as abort(500)
|
||||
return render_template("500.html"), 500
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
# wrapped unhandled error
|
||||
return render_template("500_unhandled.html", e=original), 500
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
Registering Custom Exceptions
|
||||
-----------------------------
|
||||
|
||||
You can create your own custom exceptions by subclassing
|
||||
:exc:`werkzeug.exceptions.HTTPException`. As shown above, integer HTTP codes
|
||||
are interchangable when registering handlers. (``BadRequest.code == 400``)
|
||||
|
||||
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||
by Werkzeug. Instead, define a subclass of
|
||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||
register and raise that exception class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||
code = 507
|
||||
description = 'Not enough storage space.'
|
||||
|
||||
def handle_507(e):
|
||||
return 'Not enough storage space!', 507
|
||||
|
||||
app.register_error_handler(InsufficientStorage, handle_507)
|
||||
|
||||
# during an request
|
||||
raise InsufficientStorage()
|
||||
|
||||
Handlers can be registered for any exception class that inherits from Exception.
|
||||
|
||||
|
||||
Unhandled Exceptions
|
||||
--------------------
|
||||
|
||||
If an exception is raised in the code while Flask is handling a request and
|
||||
there is no error handler registered for that exception, a "500 Internal Server
|
||||
Error" will be returned instead. See :meth:`flask.Flask.handle_exception` for
|
||||
information about this behavior.
|
||||
|
||||
Custom error pages
|
||||
------------------
|
||||
Further Examples
|
||||
````````````````
|
||||
|
||||
The above examples wouldn't actually be an improvement on the default
|
||||
exception pages. We can create a custom 500.html template like this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Internal Server Error{% endblock %}
|
||||
|
|
@ -290,12 +372,10 @@ It can be implemented by rendering the template on "500 Internal Server Error":
|
|||
# note that we set the 500 status explicitly
|
||||
return render_template('500.html'), 500
|
||||
|
||||
|
||||
When using the :doc:`/patterns/appfactories`:
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def internal_server_error(e):
|
||||
|
|
@ -306,7 +386,6 @@ When using the :doc:`/patterns/appfactories`:
|
|||
app.register_error_handler(500, internal_server_error)
|
||||
return app
|
||||
|
||||
|
||||
When using :doc:`/blueprints`:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
@ -324,16 +403,21 @@ When using :doc:`/blueprints`:
|
|||
blog.register_error_handler(500, internal_server_error)
|
||||
|
||||
|
||||
Blueprint Error Handlers
|
||||
------------------------
|
||||
|
||||
In blueprints errorhandlers will simply work as expected; however, there is a caveat
|
||||
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||
access. This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint error handler it
|
||||
should run if given an invalid URL. If you would like to execute different
|
||||
handling strategies for these errors based on URL prefixes, they may be defined
|
||||
at the application level using the ``request`` proxy object:
|
||||
In :doc:`/blueprints`, most error handlers will work as expected.
|
||||
However, there is a caveat concerning handlers for 404 and 405
|
||||
exceptions. These error handlers are only invoked from an appropriate
|
||||
``raise`` statement or a call to ``abort`` in another of the blueprint's
|
||||
view functions; they are not invoked by, e.g., an invalid URL access.
|
||||
|
||||
This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint error
|
||||
handler it should run if given an invalid URL. If you would like to
|
||||
execute different handling strategies for these errors based on URL
|
||||
prefixes, they may be defined at the application level using the
|
||||
``request`` proxy object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -351,7 +435,6 @@ at the application level using the ``request`` proxy object:
|
|||
# otherwise we return our generic site-wide 404 page
|
||||
return render_template("404.html"), 404
|
||||
|
||||
|
||||
@app.errorhandler(405)
|
||||
def method_not_allowed(e):
|
||||
# if a request has the wrong method to our API
|
||||
|
|
@ -363,21 +446,16 @@ at the application level using the ``request`` proxy object:
|
|||
return render_template("405.html"), 405
|
||||
|
||||
|
||||
Returning API Errors as JSON
|
||||
----------------------------
|
||||
|
||||
More information on error handling with blueprint can be found in
|
||||
:doc:`/blueprints`.
|
||||
|
||||
|
||||
Returning API errors as JSON
|
||||
````````````````````````````
|
||||
|
||||
When building APIs in Flask, some developers realise that the builtin
|
||||
When building APIs in Flask, some developers realise that the built-in
|
||||
exceptions are not expressive enough for APIs and that the content type of
|
||||
:mimetype:`text/html` they are emitting is not very useful for API consumers.
|
||||
|
||||
Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON
|
||||
responses to API errors. :func:`~flask.abort` is called
|
||||
with a ``description`` parameter. The errorhandler will
|
||||
with a ``description`` parameter. The error handler will
|
||||
use that as the JSON error message, and set the status code to 404.
|
||||
|
||||
.. code-block:: python
|
||||
|
|
@ -397,10 +475,8 @@ use that as the JSON error message, and set the status code to 404.
|
|||
|
||||
return jsonify(resource)
|
||||
|
||||
|
||||
|
||||
We can also create custom exception classes; for instance, for an API we can
|
||||
introduce a new custom exception that can take a proper human readable message,
|
||||
We can also create custom exception classes. For instance, we can
|
||||
introduce a new custom exception for an API that can take a proper human readable message,
|
||||
a status code for the error and some optional payload to give more context
|
||||
for the error.
|
||||
|
||||
|
|
@ -443,212 +519,20 @@ This is a simple example:
|
|||
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
|
||||
A view can now raise that exception with an error message. Additionally
|
||||
A view can now raise that exception with an error message. Additionally
|
||||
some extra payload can be provided as a dictionary through the `payload`
|
||||
parameter.
|
||||
|
||||
|
||||
Generic Exception Handlers
|
||||
``````````````````````````
|
||||
|
||||
It is possible to register error handlers for very generic base classes
|
||||
such as ``HTTPException`` or even ``Exception``. However, be aware that
|
||||
these will catch more than you might expect.
|
||||
|
||||
An error handler for ``HTTPException`` might be useful for turning
|
||||
the default HTML errors pages into JSON, for example. However, this
|
||||
handler will trigger for things you don't cause directly, such as 404
|
||||
and 405 errors during routing. Be sure to craft your handler carefully
|
||||
so you don't lose information about the HTTP error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import json
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
"""Return JSON instead of HTML for HTTP errors."""
|
||||
# start with the correct headers and status code from the error
|
||||
response = e.get_response()
|
||||
# replace the body with JSON
|
||||
response.data = json.dumps({
|
||||
"code": e.code,
|
||||
"name": e.name,
|
||||
"description": e.description,
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
return response
|
||||
|
||||
# or using jsonify
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
return jsonify("code": e.code, "name": e.name, "description": e.description), e.code
|
||||
|
||||
|
||||
An error handler for ``Exception`` might seem useful for changing how
|
||||
all errors, even unhandled ones, are presented to the user. However,
|
||||
this is similar to doing ``except Exception:`` in Python, it will
|
||||
capture *all* otherwise unhandled errors, including all HTTP status
|
||||
codes. In most cases it will be safer to register handlers for more
|
||||
specific exceptions. Since ``HTTPException`` instances are valid WSGI
|
||||
responses, you could also pass them through directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
# pass through HTTP errors
|
||||
if isinstance(e, HTTPException):
|
||||
return e
|
||||
|
||||
# now you're handling non-HTTP exceptions only
|
||||
return render_template("500_generic.html", e=e), 500
|
||||
|
||||
Error handlers still respect the exception class hierarchy. If you
|
||||
register handlers for both ``HTTPException`` and ``Exception``, the
|
||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
||||
because it the ``HTTPException`` handler is more specific.
|
||||
|
||||
|
||||
Generic Error Pages
|
||||
-------------------
|
||||
|
||||
If we pass in the exception into a template as below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
return render_template("exception.html", e=e), e.code
|
||||
|
||||
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}{{ e.name }}{% endblock %}
|
||||
{% block body %}
|
||||
<h1>{{ e.code }} {{ e.name }}</h1>
|
||||
<p>{{ e.description }}</p>
|
||||
<p><a href="{{ url_for('index') }}">Go home</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
Debugging Application Errors
|
||||
````````````````````````````
|
||||
|
||||
For production applications, configure your application with logging and
|
||||
notifications as described in :doc:`/logging`. This section provides
|
||||
pointers when debugging deployment configuration and digging deeper with a
|
||||
full-featured Python debugger.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
See :doc:`/logging` for information on how to log exceptions, such as by
|
||||
emailing them to admins.
|
||||
See :doc:`/logging` for information about how to log exceptions, such as
|
||||
by emailing them to admins.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
When in Doubt, Run Manually
|
||||
---------------------------
|
||||
|
||||
Having problems getting your application configured for production? If you
|
||||
have shell access to your host, verify that you can run your application
|
||||
manually from the shell in the deployment environment. Be sure to run under
|
||||
the same user account as the configured deployment to troubleshoot permission
|
||||
issues. You can use Flask's builtin development server with `debug=True` on
|
||||
your production host, which is helpful in catching configuration issues, but
|
||||
**be sure to do this temporarily in a controlled environment.** Do not run in
|
||||
production with `debug=True`.
|
||||
|
||||
|
||||
.. _working-with-debuggers:
|
||||
|
||||
Working with Debuggers
|
||||
----------------------
|
||||
|
||||
To dig deeper, possibly to trace code execution, Flask provides a debugger out
|
||||
of the box (see :ref:`debug-mode`). If you would like to use another Python
|
||||
debugger, note that debuggers interfere with each other. You have to set some
|
||||
options in order to use your favorite debugger:
|
||||
|
||||
* ``debug`` - whether to enable debug mode and catch exceptions
|
||||
* ``use_debugger`` - whether to use the internal Flask debugger
|
||||
* ``use_reloader`` - whether to reload and fork the process if modules
|
||||
were changed
|
||||
|
||||
``debug`` must be True (i.e., exceptions must be caught) in order for the other
|
||||
two options to have any value.
|
||||
|
||||
If you're using Aptana/Eclipse for debugging you'll need to set both
|
||||
``use_debugger`` and ``use_reloader`` to False.
|
||||
|
||||
A possible useful pattern for configuration is to set the following in your
|
||||
config.yaml (change the block as appropriate for your application, of course)::
|
||||
|
||||
FLASK:
|
||||
DEBUG: True
|
||||
DEBUG_WITH_APTANA: True
|
||||
|
||||
Then in your application's entry-point (main.py),
|
||||
you could have something like::
|
||||
|
||||
if __name__ == "__main__":
|
||||
# To allow aptana to receive errors, set use_debugger=False
|
||||
app = create_app(config="config.yaml")
|
||||
|
||||
use_debugger = app.debug and not(app.config.get('DEBUG_WITH_APTANA'))
|
||||
app.run(use_debugger=use_debugger, debug=app.debug,
|
||||
use_reloader=use_debugger, host='0.0.0.0')
|
||||
|
||||
|
||||
.. _error-logging-tools:
|
||||
|
||||
|
||||
Error Logging Tools
|
||||
-------------------
|
||||
|
||||
Sending error mails, even if just for critical ones, can become
|
||||
overwhelming if enough users are hitting the error and log files are
|
||||
typically never looked at. This is why we recommend using `Sentry
|
||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
||||
available as an Open Source project `on GitHub
|
||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
||||
aggregates duplicate errors, captures the full stack trace and local
|
||||
variables for debugging, and sends you mails based on new errors or
|
||||
frequency thresholds.
|
||||
|
||||
To use Sentry you need to install the `sentry-sdk` client with extra `flask` dependencies::
|
||||
|
||||
$ pip install sentry-sdk[flask]
|
||||
|
||||
And then add this to your Flask app::
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||
|
||||
sentry_sdk.init('YOUR_DSN_HERE',integrations=[FlaskIntegration()])
|
||||
|
||||
The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get
|
||||
from your Sentry installation.
|
||||
|
||||
After installation, failures leading to an Internal Server Error
|
||||
are automatically reported to Sentry and from there you can
|
||||
receive error notifications.
|
||||
|
||||
Follow-up reads:
|
||||
|
||||
* Sentry also supports catching errors from your worker queue (RQ, Celery) in a
|
||||
similar fashion. See the `Python SDK docs
|
||||
<https://docs.sentry.io/platforms/python/>`_ for more information.
|
||||
* `Getting started with Sentry <https://docs.sentry.io/quickstart/?platform=python>`_
|
||||
* `Flask-specific documentation <https://docs.sentry.io/platforms/python/flask/>`_.
|
||||
See :doc:`/debugging` for information about how to debug errors in
|
||||
development and production.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ instructions for web development with Flask.
|
|||
templating
|
||||
testing
|
||||
errorhandling
|
||||
debugging
|
||||
logging
|
||||
config
|
||||
signals
|
||||
|
|
|
|||
|
|
@ -111,59 +111,43 @@ application). It will tell you what it tried to import and why it failed.
|
|||
The most common reason is a typo or because you did not actually create an
|
||||
``app`` object.
|
||||
|
||||
.. _debug-mode:
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
||||
(Want to just log errors and stack traces? See :doc:`errorhandling`)
|
||||
The ``flask run`` command can do more than just start the development
|
||||
server. By enabling debug mode, the server will automatically reload if
|
||||
code changes, and will show an interactive debugger in the browser if an
|
||||
error occurs during a request.
|
||||
|
||||
The :command:`flask` script is nice to start a local development server, but
|
||||
you would have to restart it manually after each change to your code.
|
||||
That is not very nice and Flask can do better. If you enable debug
|
||||
support the server will reload itself on code changes, and it will also
|
||||
provide you with a helpful debugger if things go wrong.
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: The interactive debugger in action.
|
||||
|
||||
To enable all development features (including debug mode) you can export
|
||||
the ``FLASK_ENV`` environment variable and set it to ``development``
|
||||
before running the server::
|
||||
.. warning::
|
||||
|
||||
The debugger allows executing arbitrary Python code from the
|
||||
browser. It is protected by a pin, but still represents a major
|
||||
security risk. Do not run the development server or debugger in a
|
||||
production environment.
|
||||
|
||||
To enable all development features, set the ``FLASK_ENV`` environment
|
||||
variable to ``development`` before calling ``flask run``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ export FLASK_ENV=development
|
||||
$ flask run
|
||||
|
||||
(On Windows you need to use ``set`` instead of ``export``.)
|
||||
See also:
|
||||
|
||||
This does the following things:
|
||||
|
||||
1. it activates the debugger
|
||||
2. it activates the automatic reloader
|
||||
3. it enables the debug mode on the Flask application.
|
||||
|
||||
You can also control debug mode separately from the environment by
|
||||
exporting ``FLASK_DEBUG=1``.
|
||||
|
||||
There are more parameters that are explained in :doc:`/server`.
|
||||
|
||||
.. admonition:: Attention
|
||||
|
||||
Even though the interactive debugger does not work in forking environments
|
||||
(which makes it nearly impossible to use on production servers), it still
|
||||
allows the execution of arbitrary code. This makes it a major security risk
|
||||
and therefore it **must never be used on production machines**.
|
||||
|
||||
Screenshot of the debugger in action:
|
||||
|
||||
.. image:: _static/debugger.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: screenshot of debugger in action
|
||||
|
||||
More information on using the debugger can be found in the `Werkzeug
|
||||
documentation`_.
|
||||
|
||||
.. _Werkzeug documentation: https://werkzeug.palletsprojects.com/debug/#using-the-debugger
|
||||
|
||||
Have another debugger in mind? See :ref:`working-with-debuggers`.
|
||||
- :doc:`/server` and :doc:`/cli` for information about running in
|
||||
development mode.
|
||||
- :doc:`/debugging` for information about using the built-in debugger
|
||||
and other debuggers.
|
||||
- :doc:`/logging` and :doc:`/errorhandling` to log errors and display
|
||||
nice error pages.
|
||||
|
||||
|
||||
HTML Escaping
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue