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:
Use
structlog.configure()
withstructlog.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’sbind()
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()
andstructlog.threadlocal.get_merged_threadlocal()
.
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 ofbind()
). 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 andnew()
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 thusmultiprocessing
(unless configured to use thespawn
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).Added 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.
Added 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.
Added 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.
Added 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.
Added 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.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()[source]¶
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)[source]¶
Wrap a dict-like class and return the resulting class.
The wrapped class and used to keep global in the current thread.
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. Usebound_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.