Reviewed 6 September 2026

Part II: Practices

Logging

Library Notes
Loguru Single-object API, simple setup. Configures global state, so it is generally avoided in libraries.
structlog Structured logging with processor chains; integrates with stdlib logging.
logging Stdlib. Handlers, filters, hierarchical loggers, dictConfig.
OpenTelemetry Traces, metrics and logs with context propagation across services; vendor-neutral exporters.
Logfire Observability platform from the Pydantic team, built on OpenTelemetry.

The stdlib logging module is configured once, at the application entry point, usually with dictConfig. Every module takes a logger named after itself; a library adds a NullHandler once, to its top-level logger, and leaves the rest of the configuration to the application.

logger = logging.getLogger(__name__)                           # every module

logging.getLogger(__name__).addHandler(logging.NullHandler())  # library __init__.py only

Structured records are emitted as fields rather than formatted strings, which makes them queryable in log aggregation systems. With structlog:

log = structlog.get_logger()
log.info("rows_processed", symbol=symbol, rows=n, duration_s=elapsed)

The stdlib takes the same fields through extra={...}, and only a formatter that serialises them, such as a JSON one, puts them in the output.

For services, traces are usually more informative than logs; log records carry the trace and span IDs so the two can be correlated.