The focus of structlog has always been to be flexible to a fault. The goal is that a user can use it with any logger of their own that is wrapped by structlog.
That’s the reason why there’s an overwhelming amount of knobs to tweak, but – ideally – once you find your configuration, you don’t touch it ever again and, more importantly: don’t see any of it in your application code.
Let’s start at the end and introduce the ultimate convenience function that relies purely on configuration:
The goal is to reduce your per-file application logging boilerplate to:
import structlog logger = structlog.get_logger()
To that end, you’ll have to call
structlog.configure() on app initialization.
You can call
structlog.configure() repeatedly and only set one or more settings – the rest will not be affected.
If necessary, you can always reset your global configuration back to default values using
That can be handy in tests.
At any time, you can check whether and how structlog is configured using
>>> structlog.is_configured() False >>> structlog.configure(logger_factory=structlog.stdlib.LoggerFactory) >>> structlog.is_configured() True >>> cfg = structlog.get_config() >>> cfg["logger_factory"] <class 'structlog.stdlib.LoggerFactory'>
Since you’ll call
structlog.get_logger() in module scope, it runs at import time before you had a chance to configure structlog.
Therefore it returns a lazy proxy that returns a correctly configured bound logger on its first call to one of the context-managing methods like
Thus, you must never call
bind() in module or class scope because , you will receive a logger configured with structlog’s default values.
initial_values to achieve pre-populated contexts.
To enable you to log with the module-global logger, it will create a temporary bound logger on each call.
Therefore if you have nothing to bind but intend to do lots of log calls in a function, it makes sense performance-wise to create a local logger by calling
new() without any parameters.
See also Performance.
What To Configure#
You can find the details in the API documentation of
structlog.configure(), but let’s introduce the most important ones at a high level first.
You’ve met Bound Loggers in the last chapter.
They’re the objects returned by
get_logger() and allow to bind key-value pairs into their private context.
You can configure their type using the
Whenever you bind or unbind data to a bound logger, this class is instantiated with the new context and returned.
We’ve already talked about wrapped loggers responsible for the output, but until now we haven’t explained where they come from until now.
Unlike with bound loggers, you often need more flexibility when instantiating them.
Therefore you don’t configure a class; you configure a factory using the
It’s a callable that returns the logger that gets wrapped and returned.
In the simplest case, it’s a function that returns a logger – or just a class.
But you can also pass in an instance of a class with a
__call__ method for more complicated setups.
These will be passed to the logger factories.
For example, if you use
structlog.get_logger("a name") and configure structlog to use the standard library
LoggerFactory, which has support for positional parameters, the returned logger will have the name
For the common cases of standard library logging and Twisted logging, structlog comes with two factories built right in:
So all it takes to use standard library
logging for output is:
>>> from structlog import get_logger, configure >>> from structlog.stdlib import LoggerFactory >>> configure(logger_factory=LoggerFactory()) >>> log = get_logger() >>> log.critical("this is too easy!") event='this is too easy!'
By using structlog’s
structlog.stdlib.LoggerFactory, it is also ensured that variables like function names and line numbers are expanded correctly in your log format.
See Standard Library Logging for more details.
structlog.get_logger() without configuration gives you a perfectly useful
We don’t believe silent loggers are a sensible default.
You will meet Processors in the next chapter.
They are configured using the
processors keyword that takes an
Iterable of callables that act as processors.