Why call logging.getLogger(__name__) in each module instead of logging.info() directly?
answer
- Names are not decoration
- Dots build a tree
- Import path becomes the logger name
- Records climb toward the root
- One registry, one object per name
basics
~20 slogging.getLogger(name) returns a logger named after the module's import path, so every record says where it came from and configuration can be aimed at one package. Calling logging.info goes through the root logger, which loses that origin.
solid answer
~40 sLoggers live in a dotted hierarchy keyed by name: `app.billing.invoices` sits under `app.billing`, under `app`, under the root logger that `logging.getLogger()` returns when given no name. `__name__` is the module's import path, so `getLogger(__name__)` puts every module at the right point in that tree for free. Two things follow. Records carry the logger's name, so a formatter with `%(name)s` shows the origin without threading it through by hand. And configuration becomes hierarchical: a handler or level set on `app.billing` covers everything beneath it, because a record walks up its ancestors and is written by every handler it meets on the way. Module-level `logging.info(...)` is a shortcut onto the root logger, so every module shares one name and one set of handlers and nothing can be tuned independently.
code
python · 9 linesimport logging
logging.getLogger("app")
logging.getLogger("app.billing")
child = logging.getLogger("app.billing.invoices")
print(child.name, "->", child.parent.name, "->", child.parent.parent.name)
orphan = logging.getLogger("tools.reindex.worker")
print(orphan.name, "->", orphan.parent.name)go deeper
Be ready to say what __name__ evaluates to and why the returned logger is shared rather than new. Knowing that a record travels up to the root logger is enough at this level.
Explain the mechanics: a name-keyed registry, dots defining parentage, placeholders for ancestors nobody asked for, and the record walking up the chain to whatever handlers it finds.
Show the operational payoff — turning one dotted prefix verbose during an incident without touching the rest, and keeping library loggers named but unconfigured so the application owns the destinations.
Own the convention across services: one naming scheme derived from import paths, configuration applied at prefixes, and a standard that forbids module-level logging calls in shared code.
### What the call actually does `logging.getLogger(name)` is a lookup in a process-wide registry held by the logging module's manager, not a constructor. Ask for the same name twice and you get the same `logging.Logger` object back, from anywhere in the process. That is why the call is safe at module import time, safe in a hot function, and why "just call it again" is never a way to get a fresh logger. `logging.getLogger()` with no argument returns the root logger, the one ancestor every other logger ultimately reports to. ### The dotted hierarchy The name is not decoration. Dots define parentage: `app.billing.invoices` is a child of `app.billing`, which is a child of `app`, which is a child of the root. `__name__` inside a module is exactly its import path — `app.billing.invoices` for `app/billing/invoices.py`, and the package name inside a package's `__init__.py` — so `getLogger(__name__)` mirrors your package layout into the logger tree with no bookkeeping. One subtlety: the intermediate names are not created for you. If nothing has ever called `getLogger("app")`, the manager stores a lightweight placeholder for it and the child's parent is whatever real ancestor exists — often the root logger directly. Ask for `"app"` later and the manager rewires the existing child so its parent becomes the new logger. The tree is always consistent by the time a record travels, but a parent chain printed at startup can look shorter than the dotted name suggests. ### Why the tree matters at emit time When you call `logger.warning(...)`, the logger builds a record and then walks itself and each ancestor in turn, handing the record to every handler it finds, until it reaches the root or a logger that has propagation switched off. So a single handler installed on the root logger is enough to give the whole application output, and a handler installed on `app.billing` receives everything from `app.billing` and its descendants and nothing else. Configuration aimed at a dotted prefix is the standard way to make one noisy subsystem quiet or one interesting subsystem verbose while leaving the rest alone. The name also travels on the record itself, so a formatter can print it. Without per-module loggers every line claims to come from `root`, and the first thing you want during an incident — which module said this? — is gone. ### Why not the module-level functions `logging.info(...)`, `logging.warning(...)` and friends are convenience wrappers around the root logger. They are fine in a five-line script. In an application they are corrosive: every module is anonymous, every module shares one level and one handler set, and a library that uses them writes into the application's root logger as if it owned it. The module-level functions also install a default handler on the root the first time they are used, which quietly makes the first caller the owner of your logging configuration. ### The conventions that come with it Create the logger once at module scope — `logger = logging.getLogger(__name__)` under the imports — and use it everywhere in that file, including inside classes; a logger per class buys almost nothing because the record already carries the module, function and line. Never build the name by hand from strings that can drift from the real import path, and never derive it from a file path: `__name__` is already the correct answer and it keeps working when the module moves. In the file you run directly, `__name__` is `"__main__"`, so that logger sits under the root rather than under your package — which is usually harmless because entry-point scripts configure logging rather than rely on being targeted by name. ### What it does not do Naming a logger is not configuring one. `getLogger` never opens a file, never installs a handler and never decides a format; a freshly-named logger has an empty handler list and no level of its own. The destination comes entirely from the handlers found on that node and its ancestors, which is why a library that only ever names loggers leaves the application in full control of where records go, and why a module that names a logger and then sees no output has a configuration problem rather than a naming one. It is also worth knowing what the name is *not* keyed to. Two processes, two interpreters or two threads share nothing here beyond the one registry inside a single interpreter: the logger is a plain object in that interpreter's memory, so a name means one thing per process. That is the whole reason configuration can be applied once at startup and be seen by every module that later asks for its own logger by name.
- What is __name__ in the file you launch directly, and what logger name does that give you?In the module run as the entry point `__name__` is `"__main__"`, so the logger is called `__main__` and hangs directly off the root logger rather than under your package. The same file imported as `myapp.cli` would instead produce `myapp.cli`, so configuration aimed at the `myapp` prefix would not match the entry-point run. It is rarely a problem, because the entry point is usually the place that configures logging rather than a place you target.
- Does logging.getLogger('app.billing') also create a logger named 'app'?No. The manager records a placeholder for a missing ancestor, not a real logger, so `app.billing`'s parent is the nearest ancestor that actually exists — the root logger if nobody has asked for `app`. When some module later calls `getLogger('app')`, the manager reparents the existing children onto it. Propagation reaches the root either way, so behaviour is unchanged; it only explains a parent chain that looks shorter than the dotted name.
- If two modules call logging.getLogger with the same string, do they share state?Yes — completely. The registry is keyed by name, so both get one object with one level, one handler list and one propagation flag. That sharing is the point for configuration, and it is also the trap behind duplicated output: a setup function that adds a handler and runs more than once keeps appending to the same shared list.
saying these in an interview costs you the question
- Thinks getLogger returns a new logger object on each call
- Uses logging.info everywhere, then cannot target one module
- Believes the dotted name is a label with no behaviour
- Hand-writes logger names that drift from the module path
- Assumes every module needs its own handler to produce output