diff --git a/docs/blueprints.rst b/docs/blueprints.rst index ce88c0f5..dbad7826 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -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 diff --git a/docs/debugging.rst b/docs/debugging.rst new file mode 100644 index 00000000..8370d624 --- /dev/null +++ b/docs/debugging.rst @@ -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 +`__. + + +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. diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 121c8a05..7f21f58f 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -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 +`_ for dealing with application errors. It's +available as an Open Source project `on GitHub +`_ and is also available as a `hosted version +`_ 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 + `__ for more information. +- `Getting started with Sentry `__ +- `Flask-specific documentation `__ +Error Handlers +-------------- -Default Error Handling -`````````````````````` +When an error occurs in Flask, an appropriate `HTTP status code +`__ 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 %} +

Page Not Found

+

What you were looking for is just not there. +

go somewhere nice + {% 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 %} -

{{ e.code }} {{ e.name }}

-

{{ e.description }}

-

Go home - {% 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 -`_ for dealing with application errors. It's -available as an Open Source project `on GitHub -`_ and is also available as a `hosted version -`_ 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 - `_ for more information. -* `Getting started with Sentry `_ -* `Flask-specific documentation `_. +See :doc:`/debugging` for information about how to debug errors in +development and production. diff --git a/docs/index.rst b/docs/index.rst index eef180d6..ec47b232 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ instructions for web development with Flask. templating testing errorhandling + debugging logging config signals diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b3028998..7a0e8cea 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -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