Python Logging
debt(d5/e5/b5/t6)
Closest to 'default linter catches the common case' (d5), pylint/ruff flag print() statements (T201) and basic logging misuse, but structured logging gaps and f-string-in-log issues need specialist rules (e.g., ruff's G/PLE rules) — slightly above d3 because not all anti-patterns are caught by defaults.
Closest to 'touches multiple files / significant refactor in one component' (e5), per quick_fix replacing print() with logging plus configuring JSON formatter, named loggers per module, and handler setup is not a one-line change — it touches every module that logs.
Closest to 'persistent productivity tax' (b5), logging applies across web and cli contexts and affects every module; misconfigured root logger or library logging creates ongoing friction for observability work.
Closest to 'serious trap' (t7), misconception is strong (print() seems fine and removable) and f-string interpolation looks idiomatic but defeats lazy evaluation — contradicts normal Python style guidance, a non-obvious gotcha most devs only learn after production pain.
Also Known As
TL;DR
Explanation
The logging hierarchy (DEBUG, INFO, WARNING, ERROR, CRITICAL) lets you filter verbosity by environment. Loggers are named (logging.getLogger(__name__)) creating a hierarchy matching the module structure. Handlers direct output to files, streams, or external services. Formatters define the output format. Never use print() for operational logging — it has no severity, no filtering, no structured output, and cannot be silenced in production without code changes.
Common Misconception
Why It Matters
Common Mistakes
- Using the root logger (logging.info()) instead of a named logger — root logger pollution breaks third-party library logging.
- Configuring logging in library code — libraries should add NullHandler only; applications configure logging.
- f-string interpolation in log messages: logger.debug(f'Value: {x}') — evaluated even when DEBUG is disabled; use % formatting.
- Not setting log level for third-party libraries — they default to WARNING; chatty libraries need explicit suppression.
Code Examples
# print() for logging — no severity, no filtering:
def process_order(order_id):
print(f'Processing order {order_id}') # No level, no off switch
result = do_processing()
print(f'Result: {result}') # Pollutes production output
# Named logger with structured output:
import logging
logger = logging.getLogger(__name__) # Named after module
def process_order(order_id: int) -> None:
logger.info('Processing order', extra={'order_id': order_id})
try:
result = do_processing()
logger.debug('Processing result', extra={'order_id': order_id, 'result': result})
except ProcessingError:
logger.error('Order processing failed', extra={'order_id': order_id}, exc_info=True)