Skip to content

v1.1.0

Choose a tag to compare

@kidoz kidoz released this 25 Aug 19:02

structguru v1.1.0

A stdlib-bridge lifecycle release. install_stdlib_bridge() gains a replace option that releases the previous managed bridge before installing the new one — last call wins — so applications whose logging setup legitimately runs more than once per process (multiple entrypoints, repeated setup in test suites) no longer need a hand-rolled scan-and-uninstall loop. The fail-fast default is unchanged; upgrade in place from any v1.0.x.

Features

  • replace=True on install_stdlib_bridge(). With an active bridge, the old one is released with full uninstall_stdlib_bridge() semantics — detached from the root logger, closed, its existing-loggers snapshot restored — and then the new bridge installs. With no active bridge it behaves exactly like a plain install; with the default replace=False, a second install still raises RuntimeError.

    bridge = install_stdlib_bridge(
        level="INFO",
        suppress_loggers=("urllib3", "botocore"),
        replace=True,  # last call wins instead of raising "already installed"
    )
  • STRUCTGURU_STDLIB_REPLACE brings the same capability to install_stdlib_bridge_from_env(), parsed with the same strict boolean conventions as the other STRUCTGURU_STDLIB_* variables (1/0, true/false, yes/no, on/off; empty means unset; an invalid value raises ValueError before any logging state is changed). Default false.

  • Contractual swap semantics. The release-and-install sequence runs in one critical section. A record logged by another thread during the swap is delivered at most once — rendered by the outgoing or incoming bridge, raw, or not at all — never twice, and never by raising. The replacing install re-resolves disable_existing_loggers (explicit value → environment → preserve) and restores the old snapshot before applying the new policy. uninstall_stdlib_bridge() on a replaced handler is a documented no-op, and suppression levels applied by the earlier install are kept.

  • The runnable example grew a replace scenario. examples/stdlib_existing_loggers/main.py now replaces the bridge from code and from the environment, showing that a logger created between the two installs counts as existing for the replacing install's policy — and it still runs as part of the test suite so it cannot rot.

Docs

  • The README and the integrations guide document the replace option, the concurrent-swap delivery guarantees, the cumulative-suppression rule, and the new environment variable in the configuration table.

Test coverage

  • 12 new tests pin the replace semantics: swap with exactly-once delivery, the unchanged fail-fast message, no-op and detached-bridge paths, both disable_existing_loggers transitions asserting restore-then-apply ordering, environment re-resolution on replace, cumulative suppression, stale-handler uninstall safety, STRUCTGURU_STDLIB_REPLACE set/unset/invalid, and a concurrency smoke test with a live logger.add() sink — 463 Python tests in total; the Rust core is untouched.