In a new Django project with no LOGGING setting, where do Django's log records go when DEBUG is True, and when it is False?
answer
- two filters keyed on one setting
- console versus email
- DEFAULT_LOGGING in django.utils.log
- ADMINS must not be empty
basics
~20 sDEFAULT_LOGGING sends the django logger's INFO and above to the console when DEBUG is True. When DEBUG is False, only ERROR and above leave, emailed to ADMINS by AdminEmailHandler; everything else is dropped. runserver's django.server lines always print.
solid answer
~40 sWithout a `LOGGING` setting Django applies `DEFAULT_LOGGING`. The `django` logger has two handlers: `console` at `INFO`, gated by `RequireDebugTrue`, and `mail_admins`, an `AdminEmailHandler` at `ERROR`, gated by `RequireDebugFalse`. So in development, Django's `INFO`-and-above records print to stderr; in production only `ERROR` and `CRITICAL` records leave, as emails to `ADMINS`, and warnings such as 404s are discarded. `ADMINS` is empty by default, in which case even the email is skipped. `django.server` has its own console handler and does not propagate, so `runserver` request lines always print. SQL logging sits at `DEBUG` level on `django.db.backends`, below the `django` logger's `INFO`, so it stays hidden until configured.
code
python · 11 linesimport logging
from django.http import HttpResponse
logger = logging.getLogger(__name__)
def ping(request):
logger.info("ping") # dropped by default: no handler, below WARNING
logger.warning("pong") # printed to stderr by Python's last-resort handler
return HttpResponse("ok")go deeper
Recall the two default routes: console for INFO and above when DEBUG is True, email to ADMINS for errors when DEBUG is False.
Explain DEFAULT_LOGGING's filters, handlers and the propagate=False on django.server, and why SQL stays hidden at DEBUG level.
Recognise the silent-production trap, DEBUG False with empty ADMINS and no console handler, and fix it before launch.
Decide where a Django service's logs and error alerts should flow in production rather than leaving it to framework defaults.
## Logging is configured before your code runs Django configures Python's `logging` module inside `django.setup()`, before any app is loaded, so loggers are ready by the time a view runs. With no `LOGGING` setting (its default is an empty dict), the configuration applied is Django's own **`DEFAULT_LOGGING`**, a dictConfig dictionary in `django/utils/log.py`. ## What DEFAULT_LOGGING contains - **Two filters.** `require_debug_true` (`RequireDebugTrue`) lets a record through only when `settings.DEBUG` is `True`; `require_debug_false` (`RequireDebugFalse`) only when it is `False`. - **Three handlers.** - `console`: a `StreamHandler` at level `INFO` guarded by `require_debug_true`. - `mail_admins`: an `AdminEmailHandler` at level `ERROR` guarded by `require_debug_false`. - `django.server`: a `StreamHandler` with a special formatter for `runserver` request lines. - **Two loggers.** `django` at level `INFO` with the `console` and `mail_admins` handlers, and `django.server` at `INFO` with its own handler and `propagate: False`. Every other Django logger, such as `django.request` or `django.security.csrf`, is a child of `django` and propagates up to it. ## The resulting behaviour | Condition | Records from the `django` hierarchy | Where they go | |---|---|---| | `DEBUG = True` | `INFO` and above | the console (stderr) | | `DEBUG = False` | `ERROR` and `CRITICAL` | emailed to `ADMINS` by `AdminEmailHandler` | | `DEBUG = False` | `INFO`, `WARNING` | discarded | | either | `django.server` records (only `runserver`) | the console | Three consequences surprise people: 1. **Production is quiet.** With `DEBUG = False` and no `LOGGING`, a 404 warning or an informational message from Django is dropped, and a 500 only produces an email. 2. **The email needs `ADMINS`.** `ADMINS` defaults to an empty list, and `AdminEmailHandler` returns without sending when it is empty, so a fresh production deployment reports its 500s nowhere at all. 3. **SQL is not shown even in development.** Query logging goes to `django.db.backends` at `DEBUG` level, below the `django` logger's `INFO` threshold, so it is filtered out until you configure that logger. ## Your own loggers Code in your apps that calls `logging.getLogger(__name__)` produces loggers named after your modules, outside the `django` hierarchy. `DEFAULT_LOGGING` gives them no handler, so Python's last-resort behaviour applies: records at `WARNING` and above are written to stderr, and `INFO` and `DEBUG` records are dropped. How the logger hierarchy and propagation work in general is a Python logging topic. ## Seeing it in a fresh project - Run `runserver` with `DEBUG = True`: request lines come from `django.server`; any `INFO` record from Django's own loggers (the autoreloader's file-change notices, for example) appears on the console too. - Set `DEBUG = False`, add `ALLOWED_HOSTS`, and raise an exception in a view: nothing is printed by the `django` logger's handlers, and an email is attempted only if `ADMINS` is set. ## A minimal production baseline Most teams make two changes before launch: records must reach the process output, and error emails must actually have recipients. ```python ADMINS = ["[email protected]"] LOGGING = { "version": 1, "disable_existing_loggers": False, "handlers": { "console": {"class": "logging.StreamHandler"}, }, "root": {"handlers": ["console"], "level": "INFO"}, } ``` - The root handler catches your apps' loggers at `INFO` and above. - Django's own loggers are untouched, so `django` keeps its default `console` (development only) and `mail_admins` handlers, and its records also propagate to the root console, which prints them in every environment. In development that means Django's `INFO` lines appear twice; redefining the `django` logger, listing its handlers again, with `propagate: False` removes the duplicate. - `disable_existing_loggers: False` is what keeps the Django loggers alive; the reason is its own topic. ## Changing it You change this behaviour with the `LOGGING` setting, which Django applies **on top of** `DEFAULT_LOGGING`, or by setting `LOGGING_CONFIG = None` to skip Django's configuration entirely. A typical first step in production is a console handler for the `django` logger at `INFO`, so that records reach the process's output and whatever collects it.
- Why does a Django production server with DEBUG = False and no LOGGING show nothing when a view raises?The `console` handler is gated by `RequireDebugTrue`, so it drops every record, and the only other handler on the `django` logger, `mail_admins`, emails the error. If `ADMINS` is empty, which is the default, `AdminEmailHandler` returns without sending. The 500 is then reported nowhere until you add a console or file handler in `LOGGING`.
saying these in an interview costs you the question
- Django logs nothing unless you define LOGGING
- With DEBUG = True Django prints every SQL query by default
- Production errors are written to the console by default
- By default, errors are emailed to ADMINS during development too
- ADMINS has a default address, so emails always go somewhere