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

  1. No LOGGING setting, so Django's default applies (console only with debug on).
  2. The level is too high. logger.info(...) is dropped because the effective level is WARNING.
  3. Output is buffered. Lines appear minutes later, in bursts, or never if the process is killed. Common in containers.
  4. gunicorn's own logs go nowhere. Without --access-logfile - and --error-logfile - they are not written to the console, and print() from the app is lost without --capture-output.
  5. Logs go to a file on a disk you can't read, or one that vanishes when the container is replaced.
  6. disable_existing_loggers is true (the default in a dictConfig), 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

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

Get startedSee pricing

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.