Skip to content

v1.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Oct 19:19

structguru v1.5.0

A resilience and security release. A retired sink's destructor could deadlock the dispatcher during removal or reconfiguration; a stalled sink could hold flush(), shutdown(), and reconfiguration open forever; and the warnings those failures emit could print the exception's text — including a record's secrets — into diagnostics that bypass redaction by design. The minor version carries two behavior changes to read before you upgrade: adding the same logging.Handler twice now raises ValueError, and sink-failure and fork-restart diagnostics no longer include the exception text. Free-threaded CPython 3.15t joins the 3.14t wheels. No breaking API changes; upgrade in place from any v1.x.

Behavior changes

  • Adding the same logging.Handler twice raises ValueError until it has been removed. A second add() used to register the handler again and deliver every rendered record to it twice; the first registration's delivery and ownership are now preserved. A handler removed through any Logger can be added again, and a failed registration rolls its root-logger reservation back instead of leaving the handler unusable.

  • Sink-failure and fork-restart diagnostics never stringify the exception, reporting only its type: structguru callable sink raised 1 time(s), latest ConnectionError. Diagnostic warnings bypass record redaction, so an exception whose text embedded the record — or a credential — it was writing could put the unredacted value into a warning, and a __str__ with side effects ran on the delivery thread and on the fork path. The error is still counted in callable_errors and stream_errors, and the fork diagnostic still names the type.

Features

  • CPython 3.15 support. The abi3 wheel already covers 3.15 unchanged, and free-threaded 3.15t wheels join the 3.14t ones for Linux x86_64, macOS arm64, and Windows x64. The 3.15 source-gate leg returns once PyYAML ships cp315 wheels — it has none for 3.15 and its sdist build crashes the CI runners today — while the free-threaded 3.15t gate already runs on 3.15.

Fixes

Lifecycle

  • Removing or replacing a callable sink releases retired Python callbacks after unlocking the dispatcher. remove(), configure(), sync(), and disable() dropped the dispatcher's fork snapshot — and with it the last reference to a retired callback — while holding the registry lock. A callback whose __del__ re-enters the dispatcher, as a sink that calls metrics() or add() from its destructor does, deadlocked the process: all eight transition/cache combinations hung before the fix and complete cleanly after it, pinned by a subprocess regression test.

  • flush(), shutdown(), and reconfiguration no longer block forever when the native writer's sink stops making progress — a full stdout pipe whose reader has stopped, for example. A writer stuck for 10 seconds is abandoned with a warning naming the number of undrained records, matching the stall limit callable sinks already had.

  • A panic inside a native sink no longer kills the writer thread silently, which used to leave flush() waiting forever and producers enqueueing into a queue nobody drained. The interruption is counted in sink_errors, the writer closes, and blocked flushers and producers wake up.

Docs

  • The documentation is restructured into tutorials, how-to guides, reference, and explanation (Diátaxis), and the site is built in CI and through make docs.
  • The Sentry how-to no longer reads as a redaction guarantee: the raw exception passed to sentry_sdk.capture_exception lets the SDK attach unredacted frame locals unless it is initialised with include_local_variables=False, and the docs now say so.

Test coverage

  • 19 new Python test cases and 4 new Rust tests since v1.4.0 — 977 Python tests and 99 Rust tests in total, with line coverage at 93.8%. The dispatcher fix is pinned by a subprocess test that hangs on the previous build; the diagnostics change by a test whose exception's __str__ records being called and never is.