~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