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.

Therefore, as of 22.1.0, the structlog.threadlocal module is frozen and will be removed after May 2023.

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:

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

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 re-uses 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)[source]#

Put keys and values into the thread-local context.

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

New in version 19.2.0.

Deprecated since version 22.1.0.

structlog.threadlocal.unbind_threadlocal(*keys)[source]#

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

New in version 20.1.0.

Deprecated since version 22.1.0.

structlog.threadlocal.bound_threadlocal(**kw)[source]#

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.

New in version 21.4.0.

Deprecated since version 22.1.0.

structlog.threadlocal.get_threadlocal()[source]#

Return a copy of the current thread-local context.

New in version 21.2.0.

Deprecated since version 22.1.0.

structlog.threadlocal.get_merged_threadlocal(bound_logger)[source]#

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

New in version 21.2.0.

Deprecated since version 22.1.0.

structlog.threadlocal.merge_threadlocal(logger, method_name, event_dict)[source]#

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.

New 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()[source]#

Clear the thread-local context.

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

New in version 19.2.0.

Deprecated since version 22.1.0.

structlog.threadlocal.wrap_dict(dict_class)[source]#

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)[source]#

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)[source]#

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.