Logging

15 questions found

What's the difference between propagate=True and propagate=False on a logger?

Intermediate
By default (propagate=True), a log record handled by a child logger also passes up to its parent loggers' handlers, all the way to the root logger, potentially causing duplicate output if both the child and an ancestor have handlers. Setting propagate=False stops a logger from passing records upward, confining output to just its own handlers.
logger = logging.getLogger('myapp.module')
logger.propagate = False  # won't also trigger root logger's handlers
logger.addHandler(logging.StreamHandler())
Real-world example A module that logs very frequently sets propagate=False and attaches its own dedicated handler to avoid flooding the application's shared root-level log file.

Common follow-ups: How do you detect duplicate log lines caused by propagation?;When is propagation actually desirable?

Logging;Modules & Packaging

How do you format log output to include the timestamp, logger name, level, and message?

Beginner
A Formatter string uses LogRecord attribute placeholders like %(asctime)s, %(name)s, %(levelname)s, and %(message)s, combined into a single format string applied to every emitted record by a handler's setFormatter() call.
import logging

formatter = logging.Formatter('%(asctime)s [%(name)s] %(levelname)s: %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
logging.warning('Low disk space')
# 2024-01-15 10:30:00,123 [root] WARNING: Low disk space
Real-world example An ops team standardizes on a JSON-formatted log line (using a custom Formatter subclass) across all services so their log aggregation platform can parse fields consistently.

Common follow-ups: What other LogRecord attributes are commonly used (%(module)s, %(lineno)d)?;How would you write a JSON-output Formatter?

Logging;Debugging & Profiling

How would you write a custom logging.Filter to redact sensitive data like passwords from log messages?

Advanced
A Filter subclass implements filter(self, record), which can inspect and modify record.msg or record.args in place before returning True (keep the record) or False (drop it). Attaching this filter to a handler or logger lets you scrub sensitive patterns (like passwords or credit card numbers) from every message that passes through, centrally rather than at every call site.
import logging, re

class RedactFilter(logging.Filter):
    def filter(self, record):
        record.msg = re.sub(r'password=\S+', 'password=***', str(record.msg))
        return True

logger = logging.getLogger('app')
logger.addFilter(RedactFilter())
logger.info('Login attempt password=hunter2')  # logs 'password=***'
Real-world example A compliance-sensitive fintech application attaches a redaction Filter to all handlers to guarantee PII never lands in log files, satisfying an audit requirement.

Common follow-ups: Should redaction happen at the Filter level or at the call site?;How do you test that a Filter catches all sensitive patterns?

Exception Handling;Debugging & Profiling

What's the danger of using %-style, f-string, or .format() interpolation directly in a log call's message argument versus lazy % substitution?

Intermediate
Calling logging.info(f'User {user} logged in') always evaluates the f-string eagerly, even if the log level would suppress the message entirely -- wasting CPU on string formatting that's discarded. Using logging.info('User %s logged in', user) instead defers formatting until (and unless) the record is actually emitted, which matters for performance in hot paths with expensive-to-format arguments.
# Eager (wasteful if DEBUG is disabled):
logging.debug(f'Processing {expensive_repr(obj)}')

# Lazy (only formats if DEBUG level is active):
logging.debug('Processing %s', expensive_repr_callable())  # still eager unless wrapped
logging.debug('Processing %s', obj)  # str(obj) deferred until formatting time
Real-world example A high-throughput service avoids f-strings in debug-level logs on hot code paths, using %-style args instead, to prevent formatting overhead when DEBUG logging is disabled in production.

Common follow-ups: Does %-style substitution fully avoid the cost if args themselves are expensive to compute?;How does isEnabledFor() help guard expensive log calls?

Debugging & Profiling;String Formatting & f-strings

How does logging.NullHandler help library authors avoid the 'No handlers could be found' warning?

Advanced
Library code should never call basicConfig() or add real handlers, since that forces logging configuration onto whatever application imports it. Instead, libraries attach a NullHandler to their top-level logger, which silently discards records if the consuming application hasn't configured any handlers, avoiding the historical 'No handlers could be found for logger X' warning while leaving full control to the application.
# in mylib/__init__.py
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

# The application using mylib configures real handlers itself
import logging
logging.basicConfig(level=logging.INFO)
Real-world example An open-source library adds a NullHandler in its top-level __init__.py so it never prints unexpected output or errors when imported into an application that hasn't set up logging yet.

Common follow-ups: Why shouldn't a library call basicConfig()?;What happens if no NullHandler is attached and no handler is configured?

Modules & Packaging;Logging

Showing 11–15 of 15