示例
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 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
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()