Logging in Django: A Complete Guide to Loggers, Handlers, Filters, Formatters, Security, and Configuration

This article provides a comprehensive overview of Django’s logging system. It explains the roles of loggers, handlers, filters, and formatters, discusses security considerations, highlights the AdminEmailHandler, and shows how to configure logging using Django’s LOGGING dictionary with dictConfig.

Django logging, Python loggingloggers, handlers, filters, formattersAdminEmailHandler, LOGGING dictConfig

~10 min read • Updated Mar 15, 2026

Introduction

Python developers often use print() for quick debugging, but Django’s logging framework offers a far more powerful, structured, and flexible approach. Logging not only helps with debugging but also provides valuable insights into the health and behavior of your application.

Django builds on Python’s built‑in logging module. A logging configuration consists of four main components:

  • Loggers
  • Handlers
  • Filters
  • Formatters

Loggers

A logger is the entry point into the logging system. Each logger has a name and a log level that determines which messages it processes.

Log Levels

  • DEBUG: Low‑level system information
  • INFO: General system information
  • WARNING: Minor issues
  • ERROR: Major problems
  • CRITICAL: Critical failures

Each message is a Log Record with its own level and optional metadata such as stack traces or error codes.

If a message’s level meets or exceeds the logger’s level, it is passed to a handler; otherwise, it is ignored.


Handlers

Handlers determine what happens to log messages. They can:

  • Write to a file
  • Display output on the console
  • Send logs over the network
  • Send emails

Handlers also have log levels. A logger may have multiple handlers, each with different behaviors. For example:

  • Send ERROR and CRITICAL logs to a paging service
  • Write all logs to a file for later analysis

Filters

Filters provide fine‑grained control over which log records are processed.

They can:

  • Allow only certain messages (e.g., ERROR logs from a specific module)
  • Modify log records (e.g., downgrade ERROR to WARNING under certain conditions)

Filters can be applied to both loggers and handlers, and multiple filters can be chained.


Formatters

Formatters define how log records are converted into text. They typically use Python format strings referencing LogRecord attributes such as:

  • timestamp
  • log level
  • logger name
  • message text

Custom formatters can be created for specialized formatting needs.


Security Implications

Logs may contain sensitive information, such as:

  • HTTP request data
  • Stack traces
  • Local variable values
  • Django settings

You must understand:

  • what data is collected
  • where it is stored
  • how it is transmitted
  • who has access to it

Django allows you to filter sensitive information from error reports.


AdminEmailHandler

This built‑in handler emails site administrators when errors occur. If include_html is enabled, the email contains:

  • a full traceback
  • local variable values
  • Django settings

Because this information is sensitive, sending it via email is generally discouraged. Third‑party logging services offer safer and more flexible alternatives.


Configuring Logging

Django uses Python’s dictConfig format for logging configuration. You define your setup in the LOGGING dictionary.

This dictionary describes:

  • loggers
  • handlers
  • filters
  • formatters
  • log levels

If disable_existing_loggers=True, Django disables all default loggers. This is usually not desirable, as disabled loggers silently discard messages.

Logging is configured during Django’s setup() process, ensuring loggers are always ready for use.


Conclusion

Django’s logging system is a powerful tool for debugging, monitoring, and maintaining application health. By understanding loggers, handlers, filters, and formatters—and configuring them properly—you can build a robust, secure, and highly informative logging infrastructure for your Django projects.

Introduction

Django uses Python’s logging module and the dictConfig format to configure loggers, handlers, filters, and formatters. While the full dictConfig documentation is the authoritative source, Django provides flexible ways to tailor logging to your development or production needs.

Below are several practical examples—from simple setups to complex configurations—to help you understand what’s possible.


1. Basic Console Logging

This configuration sends all WARNING‑level (and above) messages to the console.


LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
}

Changing the root level to INFO or DEBUG will display more messages—useful during development.


2. Logging Only Django Messages

This example prints INFO‑level messages from the django logger to the console.


LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "WARNING",
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "level": os.getenv("DJANGO_LOG_LEVEL", "INFO"),
            "propagate": False,
        },
    },
}

Setting DJANGO_LOG_LEVEL=DEBUG will show Django’s verbose debug logs, including database queries.


3. Writing Logs to a File

This configuration writes all Django logs to a file instead of the console.


LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "file": {
            "level": "DEBUG",
            "class": "logging.FileHandler",
            "filename": "/path/to/django/debug.log",
        },
    },
    "loggers": {
        "django": {
            "handlers": ["file"],
            "level": "DEBUG",
            "propagate": True,
        },
    },
}

Make sure the file path is writable by the Django process.


4. Advanced Logging Configuration

This example demonstrates formatters, filters, multiple handlers, and fine‑grained logger control.


LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "verbose": {
            "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
            "style": "{",
        },
        "simple": {
            "format": "{levelname} {message}",
            "style": "{",
        },
    },
    "filters": {
        "special": {
            "()": "project.logging.SpecialFilter",
            "foo": "bar",
        },
        "require_debug_true": {
            "()": "django.utils.log.RequireDebugTrue",
        },
    },
    "handlers": {
        "console": {
            "level": "INFO",
            "filters": ["require_debug_true"],
            "class": "logging.StreamHandler",
            "formatter": "simple",
        },
        "mail_admins": {
            "level": "ERROR",
            "class": "django.utils.log.AdminEmailHandler",
            "filters": ["special"],
        },
    },
    "loggers": {
        "django": {
            "handlers": ["console"],
            "propagate": True,
        },
        "django.request": {
            "handlers": ["mail_admins"],
            "level": "ERROR",
            "propagate": False,
        },
        "myproject.custom": {
            "handlers": ["console", "mail_admins"],
            "level": "INFO",
            "filters": ["special"],
        },
    },
}

What this configuration does

  • Formatters:
    • simple: prints level + message
    • verbose: prints level, timestamp, module, process, thread, and message
  • Filters:
    • special: custom filter with arguments
    • require_debug_true: only logs when DEBUG=True
  • Handlers:
    • console: prints INFO+ messages using the simple formatter
    • mail_admins: emails ERROR+ messages to site admins
  • Loggers:
    • django: logs to console
    • django.request: sends ERROR logs to admins only
    • myproject.custom: logs INFO+ to console and ERROR+ to email

Custom Logging Configuration

If you don’t want to use dictConfig, you can override Django’s logging setup entirely.

The LOGGING_CONFIG setting defines the callable used to configure logging. By default, it points to logging.config.dictConfig.

You can replace it with your own function:


LOGGING_CONFIG = "myproject.logging.configure"

Disabling Django’s Logging Configuration

To disable Django’s automatic logging setup:


LOGGING_CONFIG = None

import logging.config
logging.config.dictConfig(...)

Note: Manual configuration runs immediately when settings are loaded, so it must appear after any settings it depends on.


Conclusion

Django’s logging system is highly flexible—from simple console output to complex multi‑handler setups with filters and custom formatters. By understanding dictConfig and Django’s logging architecture, you can build a logging system tailored to your development workflow and production monitoring needs.

Written & researched by Dr. Shahin Siami