mirror of
https://github.com/django/django.git
synced 2024-12-30 21:16:26 +00:00
d118cbb1ed
Backport of 6087bc4e15
from main.
398 lines
15 KiB
Plaintext
398 lines
15 KiB
Plaintext
=============================
|
||
How to manage error reporting
|
||
=============================
|
||
|
||
When you're running a public site you should always turn off the
|
||
:setting:`DEBUG` setting. That will make your server run much faster, and will
|
||
also prevent malicious users from seeing details of your application that can be
|
||
revealed by the error pages.
|
||
|
||
However, running with :setting:`DEBUG` set to ``False`` means you'll never see
|
||
errors generated by your site -- everyone will instead see your public error
|
||
pages. You need to keep track of errors that occur in deployed sites, so Django
|
||
can be configured to create reports with details about those errors.
|
||
|
||
Email reports
|
||
=============
|
||
|
||
Server errors
|
||
-------------
|
||
|
||
When :setting:`DEBUG` is ``False``, Django will email the users listed in the
|
||
:setting:`ADMINS` setting whenever your code raises an unhandled exception and
|
||
results in an internal server error (strictly speaking, for any response with
|
||
an HTTP status code of 500 or greater). This gives the administrators immediate
|
||
notification of any errors. The :setting:`ADMINS` will get a description of the
|
||
error, a complete Python traceback, and details about the HTTP request that
|
||
caused the error.
|
||
|
||
.. note::
|
||
|
||
In order to send email, Django requires a few settings telling it
|
||
how to connect to your mail server. At the very least, you'll need
|
||
to specify :setting:`EMAIL_HOST` and possibly
|
||
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD`,
|
||
though other settings may be also required depending on your mail
|
||
server's configuration. Consult :doc:`the Django settings
|
||
documentation </ref/settings>` for a full list of email-related
|
||
settings.
|
||
|
||
By default, Django will send email from root@localhost. However, some mail
|
||
providers reject all email from this address. To use a different sender
|
||
address, modify the :setting:`SERVER_EMAIL` setting.
|
||
|
||
To activate this behavior, put the email addresses of the recipients in the
|
||
:setting:`ADMINS` setting.
|
||
|
||
.. seealso::
|
||
|
||
Server error emails are sent using the logging framework, so you can
|
||
customize this behavior by :doc:`customizing your logging configuration
|
||
</topics/logging>`.
|
||
|
||
404 errors
|
||
----------
|
||
|
||
Django can also be configured to email errors about broken links (404 "page
|
||
not found" errors). Django sends emails about 404 errors when:
|
||
|
||
* :setting:`DEBUG` is ``False``;
|
||
|
||
* Your :setting:`MIDDLEWARE` setting includes
|
||
:class:`django.middleware.common.BrokenLinkEmailsMiddleware`.
|
||
|
||
If those conditions are met, Django will email the users listed in the
|
||
:setting:`MANAGERS` setting whenever your code raises a 404 and the request has
|
||
a referer. It doesn't bother to email for 404s that don't have a referer --
|
||
those are usually people typing in broken URLs or broken web bots. It also
|
||
ignores 404s when the referer is equal to the requested URL, since this
|
||
behavior is from broken web bots too.
|
||
|
||
.. note::
|
||
|
||
:class:`~django.middleware.common.BrokenLinkEmailsMiddleware` must appear
|
||
before other middleware that intercepts 404 errors, such as
|
||
:class:`~django.middleware.locale.LocaleMiddleware` or
|
||
:class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware`.
|
||
Put it toward the top of your :setting:`MIDDLEWARE` setting.
|
||
|
||
You can tell Django to stop reporting particular 404s by tweaking the
|
||
:setting:`IGNORABLE_404_URLS` setting. It should be a list of compiled
|
||
regular expression objects. For example::
|
||
|
||
import re
|
||
|
||
IGNORABLE_404_URLS = [
|
||
re.compile(r"\.(php|cgi)$"),
|
||
re.compile(r"^/phpmyadmin/"),
|
||
]
|
||
|
||
In this example, a 404 to any URL ending with ``.php`` or ``.cgi`` will *not* be
|
||
reported. Neither will any URL starting with ``/phpmyadmin/``.
|
||
|
||
The following example shows how to exclude some conventional URLs that browsers and
|
||
crawlers often request::
|
||
|
||
import re
|
||
|
||
IGNORABLE_404_URLS = [
|
||
re.compile(r"^/apple-touch-icon.*\.png$"),
|
||
re.compile(r"^/favicon\.ico$"),
|
||
re.compile(r"^/robots\.txt$"),
|
||
]
|
||
|
||
(Note that these are regular expressions, so we put a backslash in front of
|
||
periods to escape them.)
|
||
|
||
If you'd like to customize the behavior of
|
||
:class:`django.middleware.common.BrokenLinkEmailsMiddleware` further (for
|
||
example to ignore requests coming from web crawlers), you should subclass it
|
||
and override its methods.
|
||
|
||
.. seealso::
|
||
|
||
404 errors are logged using the logging framework. By default, these log
|
||
records are ignored, but you can use them for error reporting by writing a
|
||
handler and :doc:`configuring logging </topics/logging>` appropriately.
|
||
|
||
.. _filtering-error-reports:
|
||
|
||
Filtering error reports
|
||
=======================
|
||
|
||
.. warning::
|
||
|
||
Filtering sensitive data is a hard problem, and it's nearly impossible to
|
||
guarantee that sensitive data won't leak into an error report. Therefore,
|
||
error reports should only be available to trusted team members and you
|
||
should avoid transmitting error reports unencrypted over the internet
|
||
(such as through email).
|
||
|
||
Filtering sensitive information
|
||
-------------------------------
|
||
|
||
.. currentmodule:: django.views.decorators.debug
|
||
|
||
Error reports are really helpful for debugging errors, so it is generally
|
||
useful to record as much relevant information about those errors as possible.
|
||
For example, by default Django records the `full traceback`_ for the
|
||
exception raised, each `traceback frame`_’s local variables, and the
|
||
:class:`~django.http.HttpRequest`’s :ref:`attributes<httprequest-attributes>`.
|
||
|
||
However, sometimes certain types of information may be too sensitive and thus
|
||
may not be appropriate to be kept track of, for example a user's password or
|
||
credit card number. So in addition to filtering out settings that appear to be
|
||
sensitive as described in the :setting:`DEBUG` documentation, Django offers a
|
||
set of function decorators to help you control which information should be
|
||
filtered out of error reports in a production environment (that is, where
|
||
:setting:`DEBUG` is set to ``False``): :func:`sensitive_variables` and
|
||
:func:`sensitive_post_parameters`.
|
||
|
||
.. _`full traceback`: https://en.wikipedia.org/wiki/Stack_trace
|
||
.. _`traceback frame`: https://en.wikipedia.org/wiki/Stack_frame
|
||
|
||
.. function:: sensitive_variables(*variables)
|
||
|
||
If a function (either a view or any regular callback) in your code uses
|
||
local variables susceptible to contain sensitive information, you may
|
||
prevent the values of those variables from being included in error reports
|
||
using the ``sensitive_variables`` decorator::
|
||
|
||
from django.views.decorators.debug import sensitive_variables
|
||
|
||
|
||
@sensitive_variables("user", "pw", "cc")
|
||
def process_info(user):
|
||
pw = user.pass_word
|
||
cc = user.credit_card_number
|
||
name = user.name
|
||
...
|
||
|
||
In the above example, the values for the ``user``, ``pw`` and ``cc``
|
||
variables will be hidden and replaced with stars (``**********``)
|
||
in the error reports, whereas the value of the ``name`` variable will be
|
||
disclosed.
|
||
|
||
To systematically hide all local variables of a function from error logs,
|
||
do not provide any argument to the ``sensitive_variables`` decorator::
|
||
|
||
@sensitive_variables()
|
||
def my_function():
|
||
...
|
||
|
||
.. admonition:: When using multiple decorators
|
||
|
||
If the variable you want to hide is also a function argument (e.g.
|
||
'``user``’ in the following example), and if the decorated function has
|
||
multiple decorators, then make sure to place ``@sensitive_variables``
|
||
at the top of the decorator chain. This way it will also hide the
|
||
function argument as it gets passed through the other decorators::
|
||
|
||
@sensitive_variables("user", "pw", "cc")
|
||
@some_decorator
|
||
@another_decorator
|
||
def process_info(user):
|
||
...
|
||
|
||
.. warning::
|
||
|
||
Due to the machinery needed to cross the sync/async boundary,
|
||
:func:`~asgiref.sync.sync_to_async` and
|
||
:func:`~asgiref.sync.async_to_sync` are **not** compatible with
|
||
``sensitive_variables()``.
|
||
|
||
If using these adapters with sensitive variables, ensure to audit
|
||
exception reporting, and consider implementing a :ref:`custom filter
|
||
<custom-error-reports>` if necessary.
|
||
|
||
.. function:: sensitive_post_parameters(*parameters)
|
||
|
||
If one of your views receives an :class:`~django.http.HttpRequest` object
|
||
with :attr:`POST parameters<django.http.HttpRequest.POST>` susceptible to
|
||
contain sensitive information, you may prevent the values of those
|
||
parameters from being included in the error reports using the
|
||
``sensitive_post_parameters`` decorator::
|
||
|
||
from django.views.decorators.debug import sensitive_post_parameters
|
||
|
||
|
||
@sensitive_post_parameters("pass_word", "credit_card_number")
|
||
def record_user_profile(request):
|
||
UserProfile.create(
|
||
user=request.user,
|
||
password=request.POST["pass_word"],
|
||
credit_card=request.POST["credit_card_number"],
|
||
name=request.POST["name"],
|
||
)
|
||
...
|
||
|
||
In the above example, the values for the ``pass_word`` and
|
||
``credit_card_number`` POST parameters will be hidden and replaced with
|
||
stars (``**********``) in the request's representation inside the
|
||
error reports, whereas the value of the ``name`` parameter will be
|
||
disclosed.
|
||
|
||
To systematically hide all POST parameters of a request in error reports,
|
||
do not provide any argument to the ``sensitive_post_parameters`` decorator::
|
||
|
||
@sensitive_post_parameters()
|
||
def my_view(request):
|
||
...
|
||
|
||
All POST parameters are systematically filtered out of error reports for
|
||
certain :mod:`django.contrib.auth.views` views (``login``,
|
||
``password_reset_confirm``, ``password_change``, and ``add_view`` and
|
||
``user_change_password`` in the ``auth`` admin) to prevent the leaking of
|
||
sensitive information such as user passwords.
|
||
|
||
.. _custom-error-reports:
|
||
|
||
Custom error reports
|
||
--------------------
|
||
|
||
All :func:`sensitive_variables` and :func:`sensitive_post_parameters` do is,
|
||
respectively, annotate the decorated function with the names of sensitive
|
||
variables and annotate the ``HttpRequest`` object with the names of sensitive
|
||
POST parameters, so that this sensitive information can later be filtered out
|
||
of reports when an error occurs. The actual filtering is done by Django's
|
||
default error reporter filter:
|
||
:class:`django.views.debug.SafeExceptionReporterFilter`. This filter uses the
|
||
decorators' annotations to replace the corresponding values with stars
|
||
(``**********``) when the error reports are produced. If you wish to
|
||
override or customize this default behavior for your entire site, you need to
|
||
define your own filter class and tell Django to use it via the
|
||
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` setting::
|
||
|
||
DEFAULT_EXCEPTION_REPORTER_FILTER = "path.to.your.CustomExceptionReporterFilter"
|
||
|
||
You may also control in a more granular way which filter to use within any
|
||
given view by setting the ``HttpRequest``’s ``exception_reporter_filter``
|
||
attribute::
|
||
|
||
def my_view(request):
|
||
if request.user.is_authenticated:
|
||
request.exception_reporter_filter = CustomExceptionReporterFilter()
|
||
...
|
||
|
||
.. currentmodule:: django.views.debug
|
||
|
||
Your custom filter class needs to inherit from
|
||
:class:`django.views.debug.SafeExceptionReporterFilter` and may override the
|
||
following attributes and methods:
|
||
|
||
.. class:: SafeExceptionReporterFilter
|
||
|
||
.. attribute:: cleansed_substitute
|
||
|
||
The string value to replace sensitive value with. By default it
|
||
replaces the values of sensitive variables with stars
|
||
(``**********``).
|
||
|
||
.. attribute:: hidden_settings
|
||
|
||
A compiled regular expression object used to match settings and
|
||
``request.META`` values considered as sensitive. By default equivalent
|
||
to::
|
||
|
||
import re
|
||
|
||
re.compile(r"API|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE", flags=re.IGNORECASE)
|
||
|
||
.. versionchanged:: 4.2
|
||
|
||
``HTTP_COOKIE`` was added.
|
||
|
||
.. method:: is_active(request)
|
||
|
||
Returns ``True`` to activate the filtering in
|
||
:meth:`get_post_parameters` and :meth:`get_traceback_frame_variables`.
|
||
By default the filter is active if :setting:`DEBUG` is ``False``. Note
|
||
that sensitive ``request.META`` values are always filtered along with
|
||
sensitive setting values, as described in the :setting:`DEBUG`
|
||
documentation.
|
||
|
||
.. method:: get_post_parameters(request)
|
||
|
||
Returns the filtered dictionary of POST parameters. Sensitive values
|
||
are replaced with :attr:`cleansed_substitute`.
|
||
|
||
.. method:: get_traceback_frame_variables(request, tb_frame)
|
||
|
||
Returns the filtered dictionary of local variables for the given
|
||
traceback frame. Sensitive values are replaced with
|
||
:attr:`cleansed_substitute`.
|
||
|
||
If you need to customize error reports beyond filtering you may specify a
|
||
custom error reporter class by defining the
|
||
:setting:`DEFAULT_EXCEPTION_REPORTER` setting::
|
||
|
||
DEFAULT_EXCEPTION_REPORTER = "path.to.your.CustomExceptionReporter"
|
||
|
||
The exception reporter is responsible for compiling the exception report data,
|
||
and formatting it as text or HTML appropriately. (The exception reporter uses
|
||
:setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` when preparing the exception
|
||
report data.)
|
||
|
||
Your custom reporter class needs to inherit from
|
||
:class:`django.views.debug.ExceptionReporter`.
|
||
|
||
.. class:: ExceptionReporter
|
||
|
||
.. attribute:: html_template_path
|
||
|
||
Property that returns a :class:`pathlib.Path` representing the absolute
|
||
filesystem path to a template for rendering the HTML representation of
|
||
the exception. Defaults to the Django provided template.
|
||
|
||
.. attribute:: text_template_path
|
||
|
||
Property that returns a :class:`pathlib.Path` representing the absolute
|
||
filesystem path to a template for rendering the plain-text
|
||
representation of the exception. Defaults to the Django provided
|
||
template.
|
||
|
||
.. method:: get_traceback_data()
|
||
|
||
Return a dictionary containing traceback information.
|
||
|
||
This is the main extension point for customizing exception reports, for
|
||
example::
|
||
|
||
from django.views.debug import ExceptionReporter
|
||
|
||
|
||
class CustomExceptionReporter(ExceptionReporter):
|
||
def get_traceback_data(self):
|
||
data = super().get_traceback_data()
|
||
# ... remove/add something here ...
|
||
return data
|
||
|
||
.. method:: get_traceback_html()
|
||
|
||
Return HTML version of exception report.
|
||
|
||
Used for HTML version of debug 500 HTTP error page.
|
||
|
||
.. method:: get_traceback_text()
|
||
|
||
Return plain text version of exception report.
|
||
|
||
Used for plain text version of debug 500 HTTP error page and email
|
||
reports.
|
||
|
||
As with the filter class, you may control which exception reporter class to use
|
||
within any given view by setting the ``HttpRequest``’s
|
||
``exception_reporter_class`` attribute::
|
||
|
||
def my_view(request):
|
||
if request.user.is_authenticated:
|
||
request.exception_reporter_class = CustomExceptionReporter()
|
||
...
|
||
|
||
.. seealso::
|
||
|
||
You can also set up custom error reporting by writing a custom piece of
|
||
:ref:`exception middleware <exception-middleware>`. If you do write custom
|
||
error handling, it's a good idea to emulate Django's built-in error handling
|
||
and only report/log errors if :setting:`DEBUG` is ``False``.
|