Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Django logging is Python’s standard logging module, configured through a dictionary named LOGGING that Django passes to logging.config.dictConfig. Every message follows the same path: application code creates a record on a named logger, the logger checks its level and passes the record up the hierarchy, and handlers decide where eligible records are written. Once you understand that path, Django’s settings stop looking like magic and become a set of controls you can reason about.

The path a log message takes

Before touching any configuration, it helps to see the sequence that every record follows. The Django logging overview describes a logging configuration as built from loggers, handlers, filters, and formatters. Read in order, they form a pipeline:

  1. Record creation. Your code calls a method such as logger.warning(...). The call produces a record that carries a level, the message, and optional metadata such as traceback information.
  2. Logger check. The named logger compares the record’s level with its own level. If the record is below that threshold, it is dropped here.
  3. Propagation. If the record passes, it can be handed to the logger’s parent, and then to that parent’s parent, up to the root logger. Propagation is controlled by each logger’s propagate setting.
  4. Handler check. Each handler that receives the record applies its own level. A handler set to ERROR ignores a WARNING record even if the logger accepted it.
  5. Filters and formatters. Filters can accept, reject, or modify the record. A formatter then turns it into the text that is written to the destination.

The important point is that logger levels and handler levels are separate controls. A record has to clear every applicable check to reach a destination.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The four configuration roles

Each role answers a different question. Confusing them is the most common source of logging that appears to do nothing.

Role Question it answers What you control Typical example
Logger Where did the message come from, and is it severe enough to care about? A name and a level; whether records propagate upward logging.getLogger(__name__) in a module named shop.orders
Handler Where should eligible records go? The destination (stream, file, email) and its own minimum level A StreamHandler writing to the console
Filter Should this record proceed, and should it change? Selection or modification logic A custom filter that drops records from one noisy module
Formatter What does the output look like? The text layout of each line A format string that includes time, level, and logger name

Severity levels

Python’s levels are ordered from least to most severe. Django’s documentation describes them as follows, and these are the five levels you will use in practice:

Level Meaning in Django’s documentation Typical use in application code
DEBUG Low-level diagnostic information Values and branch decisions while investigating a problem
INFO General system information Confirmation that a routine step completed
WARNING A minor problem Something unexpected that the application recovered from
ERROR A major problem An operation failed and needs attention
CRITICAL A critical problem The service cannot continue correctly

Source: Django logging overview (development documentation).

Writing log calls in application code

In application code, create a logger at module level and use the module’s name. This makes every record show where it came from.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import logging

logger = logging.getLogger(__name__)

def create_order(order_id):
    logger.info("Creating order %s", order_id)
    try:
        ...
    except ValueError:
        logger.exception("Order %s failed validation", order_id)
        raise

Pass values as arguments to the logging call rather than building the string yourself with an f-string. The logging module only formats the message if the record is actually emitted, so arguments avoid unnecessary work for records that are filtered out. Choose the level by the severity of the event, not by where you want the output to appear. Where to send it is a configuration decision.

How Django sets up logging

Django configures logging during its general setup process, so code that runs after setup can use loggers without extra steps. The configuration comes from the LOGGING setting, a dictionary in the format expected by dictConfig. Django merges your dictionary with its own defaults.

The LOGGING_CONFIG setting names the callable that applies the dictionary. Its default is logging.config.dictConfig. Setting LOGGING_CONFIG to None turns off Django’s automatic configuration step. It does not stop your code from calling logging functions; it only means nothing configures the handlers for you, so you must set them up yourself. Details are in the Django 6.1 settings reference.

A minimal working configuration

Start with a single console handler on the root logger. This is enough to confirm that records reach your terminal.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
# settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {"class": "logging.StreamHandler"},
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
}

Then extend it in this order, testing after each step:

  1. Add a formatter. Define a formatters entry with a format string that includes levelname, name, and message, and reference it from the handler with "formatter": "verbose". Once the logger name appears in each line, you can see which module produced it.
  2. Add a named application logger. Add a loggers entry for your package, for example "shop", with "level": "INFO" and "handlers": ["console"]. Set "propagate": False only if you want that logger’s records to stop at this level instead of reaching the root handler.
  3. Add a file destination only when needed. A handler with "class": "logging.FileHandler" and a "filename" value must point to a location that the user running the application process can write to. If it cannot, the failure is usually a startup error or missing output, so check file permissions first.

Python’s default root level is WARNING. That is why an logger.info(...) call produces nothing in the minimal example above: the record is discarded before it reaches the handler. Lowering the level on the application logger, not on the handler, is the usual fix.

Propagation and duplicate output

A child logger can pass records up to its parents, where their handlers may emit them too. This explains both of the common symptoms.

  • Each message appears twice. The child logger has its own handler and still propagates, so the parent’s handler emits the same record. Either remove the handler from one level, or set propagate to False on the child.
  • A message never appears. Walk up the hierarchy and check the level at each step. The logger may accept the record, but the handler’s level may be higher, or the record may have been filtered out before it got there.
  • Output stops after a configuration change. Check disable_existing_loggers, described next.

Why disable_existing_loggers should usually be false

When disable_existing_loggers is true, loggers that already exist when the configuration is applied are kept in place but disabled. Django’s documentation warns that these loggers silently discard records and do not propagate them. Nothing is printed and no error is raised, which makes the problem hard to notice. Django’s own examples set this value to false when extending the default configuration, and the minimal example above follows that pattern. Use true only when you are deliberately replacing every logger in the process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Django’s default handler behavior

Django ships with defaults that depend on the DEBUG setting. The following describes the Django logging reference in the development documentation; verify it against the reference for the release you run, because development documentation can differ from a released version.

Logger or setting condition Records sent Destination
django hierarchy, except django.server, with DEBUG=True INFO and higher Console
django hierarchy, with DEBUG=False ERROR and higher AdminEmailHandler
django.server INFO and higher, regardless of DEBUG Console

Source: Django logging reference (development documentation).

Because AdminEmailHandler sends mail to the addresses listed in ADMINS, a production project should confirm that list before relying on the default.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Production cautions

Verbose levels and sensitive data

The development logging overview notes that setting DJANGO_LOG_LEVEL=DEBUG can expose verbose Django debug logging, including every database query. Queries can contain personal data or values from user input. Enable DEBUG output in production only for a controlled investigation, with a time limit and a plan to turn it off.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Error emails carry request details

Error emails sent by AdminEmailHandler can include request details and tracebacks. Django’s documentation cautions about the security implications of email handling. Treat email as a notification channel, not as your log archive. Limit who receives these messages, and avoid forwarding them to shared inboxes you do not control.

Best Value

Choosing a production destination

Django documents console, file, and email handlers and notes that third-party services can provide detailed logs and access management. The table compares the options on the axes that matter operationally. It does not rank products.

Destination Where records go Searchable and retained centrally Access control Operational setup Exposure risk
Console or standard streams Process output, read by your hosting platform or process manager Depends on the platform; not stated by Django Governed by who can read the platform’s output Minimal inside Django Moderate; any request or traceback text you log is visible to anyone with that access
Local file A file the application process can write No, unless you add a shipping tool File system permissions Needs a writable path and rotation planning Moderate; depends on file permissions and retention
Email via AdminEmailHandler Addresses in ADMINS No; messages live in mailboxes Whoever controls those mailboxes Requires mail configuration and a correct ADMINS list Higher; can include request details and tracebacks
Hosted log management or application observability service An external service that receives records Usually yes; check each service’s features Set by the service’s roles and audit tools Integration and ongoing account management Depends on what you send and the service’s data terms

For a hosted service, verify its current features, data handling, and pricing directly with the provider before you choose it. Django’s documentation does not evaluate individual services.

Version considerations

The settings reference for Django 6.1 states that LOGGING_CONFIG defaults to logging.config.dictConfig. Pages in the development documentation can describe behavior that has not yet shipped, so when you read a default, check the version selector on the documentation site and match it to the Django version in your project’s requirements file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Next steps

Build the minimal configuration, confirm that a WARNING from your own module appears, then lower the application logger to INFO and confirm that the message shows without duplication. Add a formatter and a file handler only after that works, and treat production destinations as a separate decision about access, retention, and what data a record may contain.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.