Skip to main content
`Ctrl`+`K`

# Legacy Thread-local Context

Attention

The `structlog.threadlocal` module is deprecated as of structlog 22.1.0 in favor of Context Variables.

The standard library `contextvars` module provides a more feature-rich superset of the thread-local APIs and works with thread-local data, async code, and greenlets.

The plan was to remove this module after 2023, however people are reporting odd crashes with Context Variables, that we can’t reproduce. Until we find a solution, this module will not be removed, since it’s a viable workaround.

## The `merge_threadlocal` processor

structlog provides a simple set of functions that allow explicitly binding certain fields to a global (thread-local) context and merge them later using a processor into the event dict.

The general flow of using these functions is:

- Use `structlog.configure()` with `structlog.threadlocal.merge_threadlocal()` as your first processor.

- Call `structlog.threadlocal.clear_threadlocal()` at the beginning of your request handler (or whenever you want to reset the thread-local context).

- Call `structlog.threadlocal.bind_threadlocal()` as an alternative to your bound logger’s `bind()` when you want to bind a particular variable to the thread-local context.

- Use structlog as normal. Loggers act as they always do, but the `structlog.threadlocal.merge_threadlocal()` processor ensures that any thread-local binds get included in all of your log messages.

- If you want to access the thread-local storage, you use `structlog.threadlocal.get_threadlocal()` and `structlog.threadlocal.get_merged_threadlocal()`.

These functions map 1:1 to the Context Variables APIs, so please use those instead:

- `structlog.contextvars.merge_contextvars()`

- `structlog.contextvars.clear_contextvars()`

- `structlog.contextvars.bind_contextvars()`

- `structlog.contextvars.get_contextvars()`

- `structlog.contextvars.get_merged_contextvars()`

## Thread-local contexts

structlog also provides thread-local context storage in a form that you may already know from Flask and that makes the entire context global to your thread or greenlet.

This makes its behavior more difficult to reason about which is why we generally recommend to use the `merge_contextvars()` route. Therefore, there are currently no plans to re-implement this behavior on top of context variables.

### Wrapped dicts

In order to make your context thread-local, structlog ships with a function that can wrap any dict-like class to make it usable for thread-local storage: `structlog.threadlocal.wrap_dict()`.

Within one thread, every instance of the returned class will have a common instance of the wrapped dict-like class:

    >>> from structlog.threadlocal import wrap_dict
    >>> WrappedDictClass = wrap_dict(dict)
    >>> d1 = WrappedDictClass({"a": 1})
    >>> d2 = WrappedDictClass({"b": 2})
    >>> d3 = WrappedDictClass()
    >>> d3["c"] = 3
    >>> d1 is d3
    False
    >>> d1 == d2 == d3 == WrappedDictClass()
    True
    >>> d3
    <WrappedDict-...({'a': 1, 'b': 2, 'c': 3})>

To enable thread-local context use the generated class as the context class:

    configure(context_class=WrappedDictClass)

Note

Creation of a new `BoundLogger` initializes the logger’s context as `context_class(initial_values)`, and then adds any values passed via `.bind()`. As all instances of a wrapped dict-like class share the same data, in the case above, the new logger’s context will contain all previously bound values in addition to the new ones.

`structlog.threadlocal.wrap_dict` returns always a completely new wrapped class:

    >>> from structlog.threadlocal import wrap_dict
    >>> WrappedDictClass = wrap_dict(dict)
    >>> AnotherWrappedDictClass = wrap_dict(dict)
    >>> WrappedDictClass() != AnotherWrappedDictClass()
    True
    >>> WrappedDictClass.__name__
    WrappedDict-41e8382d-bee5-430e-ad7d-133c844695cc
    >>> AnotherWrappedDictClass.__name__
    WrappedDict-e0fc330e-e5eb-42ee-bcec-ffd7bd09ad09

In order to be able to bind values temporarily to a logger, `structlog.threadlocal` comes with a context manager: `structlog.threadlocal.tmp_bind()`:

    >>> log.bind(x=42)
    <BoundLoggerFilteringAtNotset(context=<WrappedDict-...({'x': 42})>, ...)>
    >>> log.msg("event!")
    x=42 event='event!'
    >>> with tmp_bind(log, x=23, y="foo") as tmp_log:
    ...     tmp_log.msg("another event!")
    x=23 y='foo' event='another event!'
    >>> log.msg("one last event!")
    x=42 event='one last event!'

The state before the `with` statement is saved and restored once it’s left.

If you want to detach a logger from thread-local data, there’s `structlog.threadlocal.as_immutable()`.

#### Downsides & caveats

The convenience of having a thread-local context comes at a price though:

Warning

- If you can’t rule out that your application reuses threads, you must remember to initialize your thread-local context at the start of each request using `new()` (instead of `bind()`). Otherwise you may start a new request with the context still filled with data from the request before.

- Don’t stop assigning the results of your `bind()`s and `new()`s!

  Do:

      log = log.new(y=23)
      log = log.bind(x=42)

  Don’t:

      log.new(y=23)
      log.bind(x=42)

  Although the state is saved in a global data structure, you still need the global wrapped logger produce a real bound logger. Otherwise each log call will result in an instantiation of a temporary BoundLogger.

  See `configuration` for more details.

- It doesn’t play well with `os.fork` and thus `multiprocessing` (unless configured to use the `spawn` start method).

## API

structlog.threadlocal.bind_threadlocal(**kw)

  Put keys and values into the thread-local context.

  Use this instead of `bind()` when you want some context to be global (thread-local).

  Added in version 19.2.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.unbind_threadlocal(*keys)

  Tries to remove bound keys from threadlocal logging context if present.

  Added in version 20.1.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.bound_threadlocal(**kw)

  Bind kw to the current thread-local context. Unbind or restore kw afterwards. Do not affect other keys.

  Can be used as a context manager or decorator.

  Added in version 21.4.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.get_threadlocal()

  Return a copy of the current thread-local context.

  Added in version 21.2.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.get_merged_threadlocal(bound_logger)

  Return a copy of the current thread-local context merged with the context from bound_logger.

  Added in version 21.2.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.merge_threadlocal(logger, method_name, event_dict)

  A processor that merges in a global (thread-local) context.

  Use this as your first processor in `structlog.configure()` to ensure thread-local context is included in all log calls.

  Added in version 19.2.0.

  Changed in version 20.1.0: This function used to be called `merge_threadlocal_context` and that name is still kept around for backward compatibility.

  Deprecated since version 22.1.0.

structlog.threadlocal.clear_threadlocal()

  Clear the thread-local context.

  The typical use-case for this function is to invoke it early in request-handling code.

  Added in version 19.2.0.

  Deprecated since version 22.1.0.

structlog.threadlocal.wrap_dict(dict_class)

  Wrap a dict-like class and return the resulting class.

  The wrapped class and used to keep global in the current thread.

  Parameters:

    dict_class (type[Dict[str, Any] | Dict[Any, Any]]) – Class used for keeping context.

  Deprecated since version 22.1.0.

structlog.threadlocal.tmp_bind(logger, **tmp_values)

  Bind tmp_values to logger & memorize current state. Rewind afterwards.

  Only works with `structlog.threadlocal.wrap_dict`-based contexts. Use `bound_threadlocal()` for new code.

  Deprecated since version 22.1.0.

structlog.threadlocal.as_immutable(logger)

  Extract the context from a thread local logger into an immutable logger.

  Parameters:

    logger (structlog.typing.BindableLogger) – A logger with possibly thread local state.

  Returns:

    `BoundLogger` with an immutable context.

  Return type:

    TLLogger

  Deprecated since version 22.1.0.