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

~6 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

Related Articles

Django Tasks Framework: A Complete Guide to Background Task Execution in Django 6.0

Django 6.0 introduces the Tasks framework, a built‑in system for defining and queuing background work outside the request–response cycle. This article explains how Tasks work, how to configure backends, how to define and enqueue tasks, how context works, and how to integrate third‑party worker systems for production environments.

Continue

Asynchronous Support in Django: A Complete Guide to Async Views, ORM, Middleware, Performance, and Safety

This article explains Django’s asynchronous (async) capabilities, including async views, ASGI support, middleware behavior, async ORM features, performance considerations, handling disconnects, and Django’s async safety protections. It also covers how to use sync_to_async(), async ORM methods, and how to safely run synchronous code in async environments.

Continue

Django System Check Framework: A Complete Guide to Writing, Registering, Running, and Testing System Checks

This article explains Django’s System Check Framework—a powerful mechanism for detecting configuration issues, validating project structure, and ensuring code quality. It covers how checks are executed, how to write custom checks, how messages work, how to register and tag checks, how to extend checks for fields and models, and how to write both unit and integration tests for system checks.

Continue

Django Signals: A Complete Guide to Listening, Connecting, Sending, and Managing Application Events

This article provides a comprehensive explanation of Django’s signal system—an event‑driven mechanism that allows decoupled applications to react to actions occurring elsewhere in the framework. It covers how to define receivers, connect signals, use decorators, handle specific senders, organize signal code, and follow best practices to avoid complexity.

Continue

Understanding Django Settings: Configuration, Environment Management, and Best Practices

This article provides a complete overview of Django’s settings system. It explains how settings files work, how to designate a settings module, how to use settings in your code, how to configure Django manually, how to secure sensitive settings, and how to work with custom default settings. It also covers the role of django.setup() for standalone scripts.

Continue

Serializing and Deserializing Django Objects: A Complete Guide to Django’s Serialization Framework

This article explains Django’s serialization framework, including how to serialize and deserialize model instances, work with subsets of fields, handle inherited models, use different serialization formats (JSON, XML, YAML, JSONL), and understand how relational fields are represented. It also covers DeserializedObject behavior and common pitfalls.

Continue