CodeSampleX

サンプル

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

コントラクト

  1. 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.
  2. bound_contextvars restores previous context values upon block exit or exception while preserving unrelated keys.
  3. reset_contextvars with tokens returned by bind_contextvars restores previous variable bindings.
  4. unbind_contextvars deletes specified keys and clear_contextvars removes all context variables.
  5. get_merged_contextvars returns current contextvars merged with bound logger context without mutating either.
  6. 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

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
# 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.
csx.json
{"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"}
pyproject.toml
[project]
name = "structlog-contextvars"
version = "0.1.0"
dependencies = [
    "structlog==26.1.0",
]
requirements.lock
structlog==26.1.0
typing_extensions==4.15.0
requirements.txt
structlog==26.1.0
test/contract.py
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()

オリジンシーダー

csx-seed