0
# Context Management
1
2
Thread-safe context binding using contextvars and deprecated thread-local storage, enabling global context that persists across function calls and async boundaries. Context management allows you to maintain structured data that automatically appears in all log entries within a given scope.
3
4
## Capabilities
5
6
### Context Variables (Recommended)
7
8
Modern context management using Python's contextvars module for thread-safe and async-safe context storage.
9
10
```python { .api }
11
def get_contextvars() -> dict[str, Any]:
12
"""
13
Return copy of structlog-specific context-local context.
14
15
Returns:
16
dict: Current context variables as key-value pairs
17
"""
18
19
def get_merged_contextvars(bound_logger) -> dict[str, Any]:
20
"""
21
Return merged context-local and bound logger context.
22
23
Args:
24
bound_logger: Bound logger instance
25
26
Returns:
27
dict: Merged context from contextvars and bound logger
28
"""
29
30
def merge_contextvars(logger, method_name, event_dict) -> EventDict:
31
"""
32
Processor that merges global context-local context into event dict.
33
34
Use this processor to automatically include context variables
35
in all log entries.
36
37
Args:
38
logger: Logger instance
39
method_name (str): Logger method name
40
event_dict (dict): Event dictionary
41
42
Returns:
43
dict: Event dictionary with context variables merged in
44
"""
45
46
def clear_contextvars() -> None:
47
"""Clear all structlog context-local context variables."""
48
49
def bind_contextvars(**kw) -> Mapping[str, contextvars.Token[Any]]:
50
"""
51
Put keys and values into context-local context.
52
53
Args:
54
**kw: Key-value pairs to bind to context
55
56
Returns:
57
dict: Mapping of keys to context variable tokens for later reset
58
"""
59
60
def reset_contextvars(**kw) -> None:
61
"""
62
Reset contextvars using tokens returned from bind_contextvars.
63
64
Args:
65
**kw: Mapping of keys to tokens from previous bind_contextvars call
66
"""
67
68
def unbind_contextvars(*keys) -> None:
69
"""
70
Remove keys from context-local context.
71
72
Args:
73
*keys: Context variable keys to remove
74
"""
75
76
def bound_contextvars(**kw) -> Generator[None, None, None]:
77
"""
78
Context manager for temporary context binding.
79
80
Binds context variables for the duration of the with block,
81
then automatically restores the previous state.
82
83
Args:
84
**kw: Key-value pairs to bind temporarily
85
86
Yields:
87
None
88
"""
89
```
90
91
### Context Variable Constants
92
93
```python { .api }
94
STRUCTLOG_KEY_PREFIX: str
95
"""Prefix used for structlog context variable names."""
96
```
97
98
### Thread-Local Context (Deprecated)
99
100
Legacy thread-local context management. Use contextvars instead for new code.
101
102
```python { .api }
103
def wrap_dict(dict_class) -> type[Context]:
104
"""
105
Wrap a dict class to be used for thread-local storage.
106
107
Args:
108
dict_class: Dictionary class to wrap
109
110
Returns:
111
type: Wrapped dictionary class with thread-local behavior
112
"""
113
114
def as_immutable(logger) -> logger:
115
"""
116
Extract context from thread-local storage into an immutable logger.
117
118
Args:
119
logger: Logger instance
120
121
Returns:
122
Logger with context extracted from thread-local storage
123
"""
124
125
def tmp_bind(logger, **tmp_values) -> Generator[logger, None, None]:
126
"""
127
Context manager for temporarily binding thread-local context.
128
129
Args:
130
logger: Logger instance
131
**tmp_values: Temporary values to bind
132
133
Yields:
134
Logger: Logger with temporary context bound
135
"""
136
137
def get_threadlocal() -> Context:
138
"""Get copy of current thread-local context."""
139
140
def get_merged_threadlocal(bound_logger) -> Context:
141
"""
142
Get merged thread-local and bound logger context.
143
144
Args:
145
bound_logger: Bound logger instance
146
147
Returns:
148
dict: Merged context
149
"""
150
151
def merge_threadlocal(logger, method_name, event_dict) -> EventDict:
152
"""
153
Processor for merging thread-local context into event dict.
154
155
Args:
156
logger: Logger instance
157
method_name (str): Logger method name
158
event_dict (dict): Event dictionary
159
160
Returns:
161
dict: Event dictionary with thread-local context merged
162
"""
163
164
def clear_threadlocal() -> None:
165
"""Clear all thread-local context."""
166
167
def bind_threadlocal(**kw) -> None:
168
"""
169
Bind key-value pairs to thread-local context.
170
171
Args:
172
**kw: Key-value pairs to bind
173
"""
174
175
def unbind_threadlocal(*keys) -> None:
176
"""
177
Remove keys from thread-local context.
178
179
Args:
180
*keys: Keys to remove
181
"""
182
183
def bound_threadlocal(**kw) -> Generator[None, None, None]:
184
"""
185
Context manager for temporary thread-local context binding.
186
187
Args:
188
**kw: Key-value pairs to bind temporarily
189
190
Yields:
191
None
192
"""
193
```
194
195
## Usage Examples
196
197
### Basic Context Variables Usage
198
199
```python
200
import structlog
201
from structlog import contextvars, processors
202
203
# Configure structlog with context variables
204
structlog.configure(
205
processors=[
206
contextvars.merge_contextvars, # Include context vars in logs
207
processors.TimeStamper(),
208
processors.JSONRenderer()
209
],
210
wrapper_class=structlog.BoundLogger,
211
)
212
213
logger = structlog.get_logger()
214
215
# Bind context variables globally
216
contextvars.bind_contextvars(
217
service="api",
218
version="1.0.0"
219
)
220
221
# All subsequent logs include the context
222
logger.info("Service started") # Includes service and version
223
logger.info("Processing request", request_id="req-123")
224
```
225
226
### Temporary Context Binding
227
228
```python
229
import structlog
230
from structlog import contextvars
231
232
structlog.configure(
233
processors=[
234
contextvars.merge_contextvars,
235
structlog.processors.JSONRenderer()
236
],
237
wrapper_class=structlog.BoundLogger,
238
)
239
240
logger = structlog.get_logger()
241
242
# Set some global context
243
contextvars.bind_contextvars(service="user-service")
244
245
# Temporarily add context for a block
246
with contextvars.bound_contextvars(user_id=123, operation="update"):
247
logger.info("Starting user update") # Includes user_id and operation
248
logger.info("Validation passed") # Includes user_id and operation
249
250
# Outside the block, temporary context is gone
251
logger.info("Update completed") # Only includes service
252
```
253
254
### Request-Scoped Context
255
256
```python
257
import structlog
258
from structlog import contextvars
259
import uuid
260
261
def process_request(request_data):
262
"""Process a request with request-scoped context."""
263
264
# Generate request ID and bind context
265
request_id = str(uuid.uuid4())
266
267
with contextvars.bound_contextvars(
268
request_id=request_id,
269
user_id=request_data.get("user_id"),
270
endpoint=request_data.get("endpoint")
271
):
272
logger = structlog.get_logger()
273
274
logger.info("Request started")
275
276
# All nested function calls will include the context
277
validate_request(request_data)
278
result = perform_operation(request_data)
279
280
logger.info("Request completed", result_count=len(result))
281
return result
282
283
def validate_request(data):
284
"""Validation function - automatically gets request context."""
285
logger = structlog.get_logger()
286
logger.info("Validating request", fields=list(data.keys()))
287
288
def perform_operation(data):
289
"""Business logic - automatically gets request context."""
290
logger = structlog.get_logger()
291
logger.info("Performing operation", operation_type=data.get("type"))
292
return ["result1", "result2"]
293
```
294
295
### Context Management Across Async Calls
296
297
```python
298
import asyncio
299
import structlog
300
from structlog import contextvars
301
302
async def handle_async_request(user_id, task_type):
303
"""Handle async request with context that spans async boundaries."""
304
305
# Bind context for this async task
306
with contextvars.bound_contextvars(
307
user_id=user_id,
308
task_type=task_type,
309
correlation_id=str(uuid.uuid4())
310
):
311
logger = structlog.get_logger()
312
313
logger.info("Async task started")
314
315
# Context is preserved across await calls
316
result = await perform_async_operation()
317
318
logger.info("Async task completed", result=result)
319
return result
320
321
async def perform_async_operation():
322
"""Async operation that inherits context."""
323
logger = structlog.get_logger()
324
325
logger.info("Starting async operation")
326
await asyncio.sleep(0.1) # Simulate async work
327
logger.info("Async operation completed")
328
329
return "success"
330
331
# Usage
332
async def main():
333
await handle_async_request(user_id=123, task_type="data_processing")
334
335
asyncio.run(main())
336
```
337
338
### Context Inspection and Management
339
340
```python
341
import structlog
342
from structlog import contextvars
343
344
structlog.configure(
345
processors=[
346
contextvars.merge_contextvars,
347
structlog.processors.JSONRenderer()
348
],
349
wrapper_class=structlog.BoundLogger,
350
)
351
352
# Bind some context
353
contextvars.bind_contextvars(
354
service="auth",
355
environment="production",
356
debug_mode=False
357
)
358
359
# Inspect current context
360
current_context = contextvars.get_contextvars()
361
print(f"Current context: {current_context}")
362
363
# Conditionally add more context
364
if current_context.get("environment") == "production":
365
contextvars.bind_contextvars(monitoring_enabled=True)
366
367
# Remove specific context
368
contextvars.unbind_contextvars("debug_mode")
369
370
# Clear all context
371
# contextvars.clear_contextvars()
372
```
373
374
### Token-Based Context Reset
375
376
```python
377
import structlog
378
from structlog import contextvars
379
380
# Bind context and get tokens
381
tokens = contextvars.bind_contextvars(
382
transaction_id="tx-123",
383
user_id=456
384
)
385
386
logger = structlog.get_logger()
387
logger.info("Transaction started")
388
389
# Later, reset specific context using tokens
390
contextvars.reset_contextvars(user_id=tokens["user_id"])
391
392
logger.info("User context reset")
393
```
394
395
### Mixed Context Sources
396
397
```python
398
import structlog
399
from structlog import contextvars
400
401
structlog.configure(
402
processors=[
403
contextvars.merge_contextvars,
404
structlog.processors.JSONRenderer()
405
],
406
wrapper_class=structlog.BoundLogger,
407
)
408
409
# Global context via contextvars
410
contextvars.bind_contextvars(service="api", version="2.0")
411
412
# Logger-specific context via bind()
413
logger = structlog.get_logger().bind(component="auth")
414
415
# Both contexts will be merged
416
logger.info("Authentication attempt", username="alice")
417
# Output includes: service, version, component, and username
418
419
# Get merged context from logger
420
bound_logger = structlog.get_logger().bind(session="abc-123")
421
merged = contextvars.get_merged_contextvars(bound_logger)
422
print(f"Merged context: {merged}")
423
```
424
425
### Error Handling with Context
426
427
```python
428
import structlog
429
from structlog import contextvars
430
431
def risky_operation():
432
"""Operation that might fail - context helps with debugging."""
433
434
with contextvars.bound_contextvars(
435
operation="data_processing",
436
batch_id="batch-456"
437
):
438
logger = structlog.get_logger()
439
440
try:
441
logger.info("Starting risky operation")
442
443
# Simulate error
444
raise ValueError("Something went wrong")
445
446
except Exception as e:
447
logger.error(
448
"Operation failed",
449
error_type=type(e).__name__,
450
error_message=str(e)
451
)
452
# Context variables (operation, batch_id) automatically included
453
raise
454
455
# The error log will include operation and batch_id for easier debugging
456
```