CodeSampleX

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

  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.

Dateien

  • NOTES.md
  • csx.json
  • pyproject.toml
  • requirements.lock
  • requirements.txt
  • test/contract.py

Quellartefakt herunterladen (tar.gz)

Quelltext

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()

Ursprungs-Seeder

csx-seed