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: structlog.get_logger().

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 structlog.reset_defaults(). That can be handy in tests.

At any time, you can check whether and how structlog is configured using structlog.is_configured() and structlog.get_config()}:

>>> structlog.is_configured()
>>> structlog.configure(logger_factory=structlog.stdlib.LoggerFactory())
>>> structlog.is_configured()
>>> 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 bind().

Thus, you must never call new() or bind() in module or class scope because , you will receive a logger configured with structlog’s default values. Use get_logger()’s 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 bind() or 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.

Wrapper Classes#

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 wrapper_class keyword.

Whenever you bind or unbind data to a bound logger, this class is instantiated with the new context and returned.

Logger Factories#

We’ve already talked about wrapped loggers responsible for the output, but 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 logger_factory keyword.

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 "a 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.

Calling structlog.get_logger() without configuration gives you a perfectly useful structlog.PrintLogger. 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.