One of Django’s nice “batteries included” features is the ability to send emails when an error is encountered. This is a great feature for small sites where minor problems would otherwise go unnoticed.
Once your site start getting lots of traffic, however, the feature turns into a liability. An error might fire off thousands of emails in rapid succession. Not only will this put extra load on your web servers, but you could also take down (or get banned from) your email server in the process.
One of the first things you want to do when setting up a high-traffic Django site is replace the default error email functionality with an error reporting service like Sentry. Once you’ve got Sentry setup, what’s the best way to disable error emails? Unfortunately, there isn’t one simple answer. Let’s dig into why…
How does Django Send the Emails?
First let’s look at how Django sends those emails. If you look at the source, you’ll find it is defined in the default logging config using standard Python logging:
As you can guess, the mail_admins
handler is the one that does the work. It is defined as:
In plain English, this says that any log messages that are ERROR
or higher in the django
Python module will get passed to django.utils.log.AdminEmailHandler
when the require_debug_false
filter evaluates to True
.
Here is an example of the default Django logging tree.
Disabling the Logger
Simply removing mail_admins
from the handlers should be easy, right? Unfortunately, the answer involves diving into the idiosyncracies of Python logging configuration.
If you search around the internet, you’ll come up with a few different options, but rarely an explanation of why they work or the potential drawbacks.
⛔️ Option 1: disable_existing_loggers
One option is to set disable_existing_loggers
to True
in your LOGGING
setting. This parameter is the source of a lot of confusion and the results of using it can be unintuitive. From the Python docs:
The default is
True
because this enables old behavior in a backward-compatible way. This behaviour is to disable any existing loggers unless they or their ancestors are explicitly named in the logging configuration.
See the effect this has on the logging tree here. Note: all loggers are flagged as “Disabled”.
The issue with this approach is that if you later define a logger for a submodule of django
, say, django.db
. As per the docs, you’ll find the default django
logger is now re-enabled, mail_admins
handler and all.
That’s pretty unintuitive behavior and for that reason, I don’t recommend this approach. When setting up logging, always set disable_existing_loggers
to False
to avoid this issue.
⛔️ Option 2: Redefine the Logger
There is a second option which is a little more surgical than option #2. By redefining the logger for django
, we will replace the handlers that are defined for it in the default config. Here’s a full logging config which will accomplish that:
See the effect this has on the logging tree here.
Unfortunately, this change wipes out any handlers set on child loggers. One such potentially helpful handler is the request logging used by Django’s runserver
command. For that reason, I don’t recommend this approach either.
✅ Option 3: Copy the Default Logger
If you’d like to keep Django’s default config and just remove the mail_admins
handler, your best bet is to simply modify the default dictionary directly.
See the effect this has on the logging tree here.
This accomplishes the goal, but also introduces some fragility into the code. A change to DEFAULT_LOGGING
in a future version of Django could break this code. In practice, you may also find you want to make more changes to the default config (for example, logging to console when DEBUG = False
).
✅ Option 4: LOGGING_CONFIG = None
Setting LOGGING_CONFIG = None
in your settings prevents Django from setting up any logging at all. This is the nuclear option.
See the effect this has on the logging tree here.
This is a valid approach and probably the best one to take once you want any sort of fine-grained control over your logging. You wouldn’t want to use this exact config in practice, but instead, take it as a starting point for building your own custom configuration to suit the needs of your project.
In the process of writing this, I discovered a few “features” in Python logging that surprised me. Brandon Rhode’s logging_tree
package was invaluable for peeking under the hood after the Django initialized the logging tree. I strongly recommend dropping it into your project to verify logging is setup as you expect.
While this post focuses on the mail_admins
functionality, in the real-world, you’ll want a more robust logging config (including a catch-all root logger, and a logger for your project module), but we’ll leave that for another post.
If you have any Django logging tips, we’d love to hear them!