Repository navigation
v1.3.1
structguru v1.3.1
A performance release for structured exceptions. One defect is fixed: with structured_exceptions=True, every logger.exception() call read and dedented source for the whole traceback before discarding all but the last exception_max_frames frames, so a deep traceback such as a RecursionError cost 1.7 ms to report twenty frames; and frames captured with exception_include_locals=True reported "line": null where every other frame carried its source line. No breaking changes; upgrade in place from any v1.x.
Fixes
-
Structured exceptions extract source only for the frames they keep. The frame builder ran
traceback.extract_tb()over the entire traceback —linecachereads and dedents the source line for each frame it visits — and then sliced the result to the lastexception_max_frames. The cost therefore scaled with the depth of the traceback rather than with the number of frames reported: aRecursionErrora thousand frames deep paid for a thousand source lookups to produce twenty. The traceback is now walked to the first kept frame before extraction runs, so the same call costs 64 µs instead of 1.7 ms, and a shallow traceback is unaffected.Frames captured with
exception_include_locals=Truenow carry thelinefield like every other frame. That path walked the raw traceback itself to reachf_localsand never looked up source, leavinglineasnull; locals now come from the same selected frames that source is extracted for, so the two modes produce identical frames apart fromlocals.configure(structured_exceptions=True, exception_include_locals=True) # before: {"filename": "app.py", "lineno": 42, "name": "handle", "line": null, "locals": {"n": "0"}} # after: {"filename": "app.py", "lineno": 42, "name": "handle", "line": "raise ValueError(\"boom\")", "locals": {"n": "0"}}
Filling in
linemakes the locals path pay for source lookup too — about 50 µs instead of 10 µs for twenty frames — which is the price of a consistent schema. Formatted tracebacks,exception_max_frames, and the locals redaction and repr limits are unchanged.
Test coverage
- 2 new Python test cases pin the fix:
extract_tb()is called on the first kept frame only, and locals and the source line are captured together on the same frame — 732 Python tests and 79 Rust tests in total, with line coverage at 94%. The in-process writer fork test now filters CPython's multi-threadedfork()deprecation warning, which it triggers on purpose, so the suite runs warning-free on 3.12+.