Beispiel
structlog 26.1.0: Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation
Verifiziertes Beispiel für pypi structlog 26.1.0: Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous…
sha256:6dec637d6343afe74048a5fc63a4dfef59e139a0fac7ba3e70880f3c05d653fa
Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen.
Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen.
MIT-0
Ausführungsbelege
Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.
- Beleggrundlage
- Signierter Vertrag bestanden
- Verifizierungsbelege
- 3
- Signaturschlüssel, die es gebaut haben
- 3
Deklarierte Umgebung
python linux x64 python python pip
Umgebungen der Verifizierungsläufe
| Umgebung | Contract | Stufen | Lauf |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-17 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
| python 3.12 · linux debian/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1python:3.12-slim@sha256:09f7da3bc104… |
2026-09-08 |
Fall
HOW- Ziel
- Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation
- Pakete
- Symbole
-
- structlog.contextvars.merge_contextvars
- structlog.contextvars.bind_contextvars
- structlog.contextvars.bound_contextvars
- structlog.contextvars.reset_contextvars
- structlog.contextvars.unbind_contextvars
- structlog.contextvars.clear_contextvars
- structlog.contextvars.get_contextvars
- structlog.contextvars.get_merged_contextvars
- Umgebung
- python
- Erstellt
- 2026-08-17T19:44:47Z
Contract
- Calling bind_contextvars has no effect on log output unless merge_contextvars is explicitly present in the processor chain, and bound logger fields override conflicting context variables.
- bound_contextvars restores previous context values upon block exit or exception while preserving unrelated keys.
- reset_contextvars with tokens returned by bind_contextvars restores previous variable bindings.
- unbind_contextvars deletes specified keys and clear_contextvars removes all context variables.
- get_merged_contextvars returns current contextvars merged with bound logger context without mutating either.
- Concurrent asyncio tasks inherit parent contextvars but mutate their context in complete isolation without leaking to siblings or the parent.
Dateien
- NOTES.md
- csx.json
- pyproject.toml
- requirements.lock
- requirements.txt
- test/contract.py
Quelltext
# structlog context variables and processor merge semantics
## search_known_solution result
`search_known_solution` returned existing published samples for `structlog` 26.1.0:
- `sha256:860cb1a769dff8104efd29459c32a26bdec09b05ed739c73df0632ac4719b4e5` (proves logger immutability upon `logger.bind()`)
- `sha256:2fa8cd02773687052536b66aa96f386d05a05b1a64ea4aae357c33957553fe75` (proves stdlib logging handoff via `dictConfig` and `ProcessorFormatter`)
This sample proves a different behavior: asynchronous context variable management, lifecycle scoping, merge precedence, and processor requirements in `structlog.contextvars`.
## Release pinned
`structlog==26.1.0`
## What a model would have written instead
A model often assumes that calling `structlog.contextvars.bind_contextvars(k=v)` automatically injects context variables into all structlog loggers by default, or that context variables override logger-bound keys during log rendering.
## How the wrong version fails
Silently with a green build: without `structlog.contextvars.merge_contextvars` in the configured processor list, calling `bind_contextvars()` succeeds without error but all context variables are silently omitted from the resulting log events. Furthermore, when `merge_contextvars` is present, `logger.bind(...)` attributes and explicit call kwargs take precedence over context variables rather than being overwritten by them.
{"case":{"believed":"Calling bind_contextvars automatically propagates context variables to all structlog loggers without requiring merge_contextvars in the processor chain.","caseId":"case:sha256:bac0d536681b7d8202112bfbd5f4220ac35f0ef2d54d5e64f5f18bab51303f48","contract":["Calling bind_contextvars has no effect on log output unless merge_contextvars is explicitly present in the processor chain, and bound logger fields override conflicting context variables.","bound_contextvars restores previous context values upon block exit or exception while preserving unrelated keys.","reset_contextvars with tokens returned by bind_contextvars restores previous variable bindings.","unbind_contextvars deletes specified keys and clear_contextvars removes all context variables.","get_merged_contextvars returns current contextvars merged with bound logger context without mutating either.","Concurrent asyncio tasks inherit parent contextvars but mutate their context in complete isolation without leaking to siblings or the parent."],"goal":"Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation","kind":"HOW","packages":["pkg:pypi/structlog@26.1.0"],"schemaVersion":1,"symbols":["structlog.contextvars.merge_contextvars","structlog.contextvars.bind_contextvars","structlog.contextvars.bound_contextvars","structlog.contextvars.reset_contextvars","structlog.contextvars.unbind_contextvars","structlog.contextvars.clear_contextvars","structlog.contextvars.get_contextvars","structlog.contextvars.get_merged_contextvars"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/structlog@26.1.0"],"schemaVersion":1,"symbols":["structlog.contextvars.merge_contextvars","structlog.contextvars.bind_contextvars","structlog.contextvars.bound_contextvars","structlog.contextvars.reset_contextvars","structlog.contextvars.unbind_contextvars","structlog.contextvars.clear_contextvars","structlog.contextvars.get_contextvars","structlog.contextvars.get_merged_contextvars"],"verifierAdapter":"python@1"}
[project]
name = "structlog-contextvars"
version = "0.1.0"
dependencies = [
"structlog==26.1.0",
]
structlog==26.1.0
typing_extensions==4.15.0
structlog==26.1.0
import asyncio
import io
import json
import structlog
from structlog.contextvars import (
bind_contextvars,
bound_contextvars,
clear_contextvars,
get_contextvars,
get_merged_contextvars,
merge_contextvars,
reset_contextvars,
unbind_contextvars,
)
def test_merge_contextvars_processor_requirement_and_precedence():
clear_contextvars()
bind_contextvars(request_id="req-100", service="auth-service", env="production")
# 1. Without merge_contextvars in the processor chain, context variables are not in event_dict
buf_without = io.StringIO()
log_without = structlog.wrap_logger(
structlog.PrintLogger(buf_without),
processors=[structlog.processors.JSONRenderer()],
)
log_without.info("event_without_processor")
record_without = json.loads(buf_without.getvalue().strip())
assert "request_id" not in record_without, "Context variables must not appear without merge_contextvars"
assert "service" not in record_without
# 2. With merge_contextvars, context variables are injected into event_dict
buf_with = io.StringIO()
log_with = structlog.wrap_logger(
structlog.PrintLogger(buf_with),
processors=[merge_contextvars, structlog.processors.JSONRenderer()],
)
log_with.info("event_with_processor")
record_with = json.loads(buf_with.getvalue().strip())
assert record_with["request_id"] == "req-100"
assert record_with["service"] == "auth-service"
assert record_with["env"] == "production"
# 3. Logger-bound context takes precedence over contextvars
log_bound = log_with.bind(service="user-service")
log_bound.info("event_with_bound_override")
record_bound = json.loads(buf_with.getvalue().strip().splitlines()[-1])
assert record_bound["service"] == "user-service", "Bound logger values must override contextvars"
assert record_bound["request_id"] == "req-100"
# 4. Explicit call kwargs take precedence over both contextvars and bound logger
log_bound.info("event_with_call_override", service="billing-service", request_id="req-override")
record_call = json.loads(buf_with.getvalue().strip().splitlines()[-1])
assert record_call["service"] == "billing-service", "Call kwargs must override bound and context variables"
assert record_call["request_id"] == "req-override"
def test_bound_contextvars_scope_and_exception_safety():
clear_contextvars()
bind_contextvars(tenant_id="tenant-alpha", trace_id="trace-001")
# Scoped context manager overrides existing and introduces temporary keys
try:
with bound_contextvars(trace_id="trace-scoped", span_id="span-123"):
scoped = get_contextvars()
assert scoped["tenant_id"] == "tenant-alpha"
assert scoped["trace_id"] == "trace-scoped"
assert scoped["span_id"] == "span-123"
raise RuntimeError("simulated error inside context scope")
except RuntimeError:
pass
# Upon exit (even on exception), scoped modifications revert and temporary keys disappear
restored = get_contextvars()
assert restored["tenant_id"] == "tenant-alpha"
assert restored["trace_id"] == "trace-001", "trace_id must revert to original value"
assert "span_id" not in restored, "Temporary span_id must be removed"
def test_token_reset_and_selective_unbinding():
clear_contextvars()
bind_contextvars(var_a="initial_a", var_b="initial_b")
# bind_contextvars returns a token map for reset_contextvars
tokens = bind_contextvars(var_a="modified_a", var_c="new_c")
assert get_contextvars()["var_a"] == "modified_a"
assert get_contextvars()["var_c"] == "new_c"
reset_contextvars(**tokens)
assert get_contextvars()["var_a"] == "initial_a", "var_a must reset to initial_a"
assert "var_c" not in get_contextvars(), "var_c must be absent after reset"
# unbind_contextvars selectively removes named keys
bind_contextvars(x="1", y="2", z="3")
unbind_contextvars("x", "z")
remaining = get_contextvars()
assert "x" not in remaining
assert "z" not in remaining
assert remaining["y"] == "2"
# clear_contextvars removes all remaining keys
clear_contextvars()
assert get_contextvars() == {}
def test_get_merged_contextvars():
clear_contextvars()
bind_contextvars(source="ctx_source", common="ctx_common")
log = structlog.get_logger().bind(logger_id="log-42", common="logger_common")
merged = get_merged_contextvars(log)
assert merged["source"] == "ctx_source"
assert merged["logger_id"] == "log-42"
assert merged["common"] == "logger_common", "Logger bound context takes precedence in get_merged_contextvars"
# Neither the contextvars nor logger instance are mutated
assert get_contextvars() == {"source": "ctx_source", "common": "ctx_common"}
async def async_worker(worker_id: str, results: dict) -> None:
# Each task binds its own identifier
bind_contextvars(worker_id=worker_id)
await asyncio.sleep(0.01)
results[worker_id] = get_contextvars()
def test_asyncio_task_isolation():
clear_contextvars()
bind_contextvars(shared_context="shared_val")
results = {}
async def run_tasks():
task1 = asyncio.create_task(async_worker("worker_1", results))
task2 = asyncio.create_task(async_worker("worker_2", results))
await asyncio.gather(task1, task2)
asyncio.run(run_tasks())
# Task 1 and Task 2 ran concurrently with separate contextvars
assert results["worker_1"] == {"shared_context": "shared_val", "worker_id": "worker_1"}
assert results["worker_2"] == {"shared_context": "shared_val", "worker_id": "worker_2"}
# Parent contextvars was unaffected by child task mutations
assert get_contextvars() == {"shared_context": "shared_val"}
def main() -> None:
test_merge_contextvars_processor_requirement_and_precedence()
test_bound_contextvars_scope_and_exception_safety()
test_token_reset_and_selective_unbinding()
test_get_merged_contextvars()
test_asyncio_task_isolation()
print("Contract passed: structlog contextvars behaviour verified.")
if __name__ == "__main__":
main()