The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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:
- 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. - 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.
- 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
propagatesetting. - 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.
- 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.
The four configuration roles
Each role answers a different question. Confusing them is the most common source of logging that appears to do nothing.
#1 Best Overall
| 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.
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.
Rank #2
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.
# 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:
- Add a formatter. Define a
formattersentry with a format string that includeslevelname,name, andmessage, and reference it from the handler with"formatter": "verbose". Once the logger name appears in each line, you can see which module produced it. - Add a named application logger. Add a
loggersentry for your package, for example"shop", with"level": "INFO"and"handlers": ["console"]. Set"propagate": Falseonly if you want that logger’s records to stop at this level instead of reaching the root handler. - 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
propagatetoFalseon 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.
Recommended Free Tools
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.
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.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteError 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.
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.
Quick Recap
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.

