structlog’s default configuration tries to be as unsurprising to new developers as possible.
Some of the choices made come with an avoidable performance price tag – although its impact is debatable.
Here are a few hints how to get most out of
structlog in production:
structlogalso comes with native log levels that are based on the ones from the standard library (read: we’ve copy and pasted them), but don’t involve
logging’s dynamic machinery. That makes them much faster. You can use
structlog.make_filtering_bound_logger()to create one.
Writing own wrapper classes is straightforward too.
Avoid (frequently) calling log methods on loggers you get back from
structlog.get_logger(). Since those functions are usually called in module scope and thus before you are able to configure them, they return a proxy that assembles the correct logger on demand.
Create a local logger if you expect to log frequently without binding:
logger = structlog.get_logger() def f(): log = logger.bind() for i in range(1000000000): log.info("iterated", i=i)
Set the cache_logger_on_first_use option to
Trueso the aforementioned on-demand loggers will be assembled only once and cached for future uses:
This has two drawbacks:
Avoid sending your log entries through the standard library if you can: its dynamic nature and flexibility make it a major bottleneck. Instead use
structlog.WriteLoggerFactoryor – if your serializer returns bytes (e.g. orjson) –
You can still configure
loggingfor packages that you don’t control, but avoid it for your own log entries.
Here’s an example for a production-ready non-asyncio
structlog configuration that’s as fast as it gets:
import logging import structlog structlog.configure( cache_logger_on_first_use=True, wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.format_exc_info, structlog.processors.TimeStamper(fmt="iso", utc=True), structlog.processors.JSONRenderer(serializer=orjson.dumps), ], logger_factory=structlog.BytesLoggerFactory(), )
It has the following properties:
Caches all loggers on first use.
Filters all log entries below the
infolog level very efficiently. The
debugmethod literally consists of
Supports Context Variables (thread-local contexts).
Adds the log level name.
Adds an ISO 8601 timestamp under the
timestampkey in the UTC timezone.
structlog.BytesLoggerFactorybecause orjson returns bytes. That saves encoding ping-pong.
Therefore a log entry might look like this: