サンプル
structlog 26.1.0: Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation
検証済みサンプル — pypi structlog 26.1.0: Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation.…
sha256:6dec637d6343afe74048a5fc63a4dfef59e139a0fac7ba3e70880f3c05d653fa
このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。
合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。
MIT-0
実行証拠
宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。
- 証拠の基準
- 署名済みコントラクト合格
- 検証レシート
- 3
- ビルドした署名鍵
- 3
宣言された環境
python linux x64 python python pip
検証実行環境
| 環境 | コントラクト | ステージ | 実行日 |
|---|---|---|---|
| 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 |
ケース
HOW- ゴール
- Inject context variables into structlog loggers with merge_contextvars, scoped lifecycles, and asynchronous task isolation
- パッケージ
- シンボル
-
- 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
- 環境
- python
- 作成日
- 2026-08-17T19:44:47Z
コントラクト
- 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.
ファイル
- NOTES.md
- csx.json
- pyproject.toml
- requirements.lock
- requirements.txt
- test/contract.py
ソース
# 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()