Guides / Django logs empty in production (logging with DEBUG = False)
Django logs empty in production (logging with DEBUG = False)
In production your logs are empty, or full of access lines and nothing from your code. Errors, print() output and your own logger.info() calls don't show up. Python and Django filter and buffer logs by default, and what works in development is turned off with DEBUG = False.
What it means
Logs only appear if a handler writes them somewhere, and the logger's level lets them through. Django's default logging configuration prints to the console only while DEBUG = True; with it off, errors are sent by email to ADMINS instead (which needs working email). Your own loggers have no handler, so Python falls back to printing WARNING and above and drops INFO. And Python buffers standard output when it isn't a terminal.
Why it happens
- No
LOGGINGsetting, so Django's default applies (console only with debug on). - The level is too high.
logger.info(...)is dropped because the effective level isWARNING. - Output is buffered. Lines appear minutes later, in bursts, or never if the process is killed. Common in containers.
- gunicorn's own logs go nowhere. Without
--access-logfile -and--error-logfile -they are not written to the console, andprint()from the app is lost without--capture-output. - Logs go to a file on a disk you can't read, or one that vanishes when the container is replaced.
disable_existing_loggersis true (the default in adictConfig), which silences loggers created before the config loads.
The fix
Log everything to standard output with one configuration. Your platform, container runtime or log service collects it from there:
# settings.py
import os
LOG_LEVEL = os.environ.get("DJANGO_LOG_LEVEL", "INFO")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"plain": {"format": "%(asctime)s %(levelname)s %(name)s: %(message)s"},
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "plain"},
},
"root": {"handlers": ["console"], "level": LOG_LEVEL},
"loggers": {
"django": {"handlers": ["console"], "level": LOG_LEVEL, "propagate": False},
},
}
In your code, use a module logger and not print:
import logging
logger = logging.getLogger(__name__)
def charge(order):
logger.info("charging order %s", order.pk)
try:
...
except Exception:
logger.exception("charge failed for order %s", order.pk) # includes the traceback
raise
Un-buffer output and send gunicorn's logs to the console:
export PYTHONUNBUFFERED=1
gunicorn myproject.wsgi:application --access-logfile - --error-logfile - --capture-output --log-level info
Making it stick in production
- Add a request ID or user ID to the log format once you have more than one instance, so you can follow a request.
- Keep
INFOin production for your own apps andWARNINGfor noisy libraries:"loggers": {"urllib3": {"level": "WARNING"}}. - Pair logs with an error tracker so exceptions reach you. If your reason for looking is a bare 500, start with Server Error (500) with DEBUG = False.
- Don't log secrets, tokens or full request bodies.
Or skip it
Django Cloud shows app, build and release logs in the dashboard, with search, time ranges and levels, or in your terminal with djangocloud logs. Logs are kept for 30 days.
pip install djangocloud-cli
djangocloud deploy
Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.
Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.