| 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 onlyStructured 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.