Question presented to candidate:
"Explain how Python's logging module works. What are loggers, handlers and formatters, how does a log call decide whether and where to output something, and how would you set up logging for an application versus a library?"
What a strong answer should cover:
- Loggers come from
logging.getLogger(__name__)and form a tree by dotted name under the root logger; each has a level, and an unset level (NOTSET) inherits the nearest ancestor's (the root defaults toWARNING) - The flow of a record: logger level check, logger filters, then every handler on that logger and, while
propagateis true, on each ancestor; each handler has its own level, filters and formatter - Handlers decide the destination (
StreamHandler,FileHandler,RotatingFileHandler,QueueHandler), formatters the text (%(asctime)s %(levelname)s %(name)s %(message)s) - Use
%-style arguments (log.info("user %s", uid)) so formatting is skipped when the record is filtered out;log.exception()insideexceptadds the traceback;extra=or aLoggerAdapteradd context fields - Configuration:
basicConfigonce at the application entry point (a no-op if the root already has handlers, unlessforce=True, added in 3.8), ordictConfigfor real apps; libraries only callgetLoggerand add aNullHandler, never configure the root - Pitfalls: duplicate lines from handlers on both a child and the root,
disable_existing_loggersdefaulting to true indictConfig, and the last-resort handler that prints WARNING and above to stderr when nothing is configured
Code / implementation expected: Optional โ configure a handler and formatter, show a module logger inheriting a level, and one propagation or exception() example.