Python Logging Hierarchy and Log Propagation
Python's logging module uses a hierarchical namespace to
manage loggers and route messages through an application. By using
dot-separated naming conventions, loggers form parent-child
relationships where records generated by child loggers are automatically
forwarded up the tree to their ancestors through a process called
propagation. This architecture centralizes handler configuration,
eliminates redundant setup, and gives developers granular control over
log levels across different modules.
The Hierarchical Logger Tree
The logger hierarchy is structured like a filesystem or Python module
path, using periods (.) as delimiters. At the base of every
hierarchy sits the Root Logger, which has no name (or is referenced as
an empty string).
When you instantiate a logger:
import logging
root = logging.getLogger() # Root logger
parent = logging.getLogger("app") # Child of root
child = logging.getLogger("app.db") # Child of "app", grandchild of rootBy convention, Python developers use
logging.getLogger(__name__). This automatically maps the
logging hierarchy to the application's package structure (e.g.,
mypackage.services.auth), ensuring that module-level
loggers inherit behavior based on their location in the project.
How Propagation Works
Propagation is the mechanism that passes a LogRecord
upward to parent loggers. Every logger has a boolean attribute named
propagate, which defaults to True.
When a log event occurs on a child logger (e.g.,
child.warning("Query failed")), the process unfolds as
follows:
- Child Level Check: The child logger checks its own
threshold level. If the log message meets or exceeds this level, a
LogRecordis created. - Child Handlers Executed: The child logger passes the record to its own attached handlers (if any exist).
- Propagation Check: The logger checks its
propagateattribute. IfTrue, it passes the record directly toparent.handlers. - Ascending the Tree: The record travels up the chain—from child, to parent, to root—triggering the handlers attached to each ancestor.
The Nuance of Ancestor Log Levels
A critical detail in Python's propagation model is that ancestor log levels are bypassed during propagation.
Once the emitting logger passes its own level check and creates a
LogRecord, ancestor loggers do not re-evaluate their
configured log levels. The ancestor loggers simply execute their
attached handlers on the incoming record. Only filters attached to
ancestor loggers or handlers can intercept the record once propagation
begins.
For example, if app.db has a level of DEBUG
and the root logger has a level of WARNING, calling
app.db.debug("message") will still cause the root logger's
handlers to process the record. The root logger's WARNING
threshold applies only to events initiated directly on the root logger,
not to propagated records.
Preventing Duplicate Logs
A common issue in Python logging is duplicate log lines. This usually
happens when a developer attaches a handler (such as a
StreamHandler printing to the console) to a child logger
while the root logger also has a console handler attached.
Because propagation is enabled by default:
- The child's handler outputs the message.
- The record propagates to the root logger.
- The root's handler outputs the message a second time.
To stop a logger from passing its records up to its parents, set
propagate to False:
logger = logging.getLogger("app.db")
logger.addHandler(logging.FileHandler("db.log"))
logger.propagate = False # Records stop here and do not reach the root loggerStandard Pattern: Centralized Handlers
The intended design of the logging module is to
configure handlers at the top level and emit records at the bottom:
- Attach formatters and handlers (console, files, remote services)
exclusively to the root logger or a top-level application logger
(
"app"). - Use
logging.getLogger(__name__)inside individual libraries and submodules without attaching handlers to them. - Allow propagation to carry all records up to the central handlers.
This pattern keeps modular code decoupled from logging destinations while maintaining total visibility across the application.