Repository navigation
fix: Isolate the open logger stack per thread with a ContextVar - #19
Merged
Merged
Conversation
SecurityLogger inherited from threading.local, but the stack of open loggers was a plain class attribute. Class attributes are not thread-local -- only the instance __dict__ is -- so every thread in the process pushed and popped onto one shared list. With concurrent requests this had two effects: - a logger was linked to a parent belonging to another request, so the audit log tree mixed unrelated requests together, - close() found a foreign logger on top of the stack and raised "Log already finished". In SecuritySession.request that happens on the way out of the context manager, after the HTTP call has already succeeded, so the caller saw an exception for a request that went through and rolled back its transaction. Failed pops also left the stack growing without bound. The stack now lives in a module level ContextVar holding an immutable tuple, which isolates it per thread and per asyncio task. A tuple is used so that the default value is never mutated in place and contexts cannot share a list by reference. SecurityLogger.loggers keeps working for reading, now returning a tuple, and the threading.local base class is dropped because the stack was the only reason for it.
xdaniel3
force-pushed
the
tda/fix/logger-stack-contextvar
branch
from
September 17, 2026 13:12
14a8358 to
c1a6ed1
Compare
Coverage Report for CI Build 36547094819Coverage increased (+0.2%) to 92.579%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
kubajecminek
approved these changes
Sep 29, 2026
xdaniel3
force-pushed
the
tda/fix/logger-stack-contextvar
branch
from
September 29, 2026 09:03
e2671b0 to
f806005
Compare
…_stack ContextVar The read-only descriptor only existed for backward compatibility. Callers read the stack directly with loggers_stack.get() instead.
xdaniel3
force-pushed
the
tda/fix/logger-stack-contextvar
branch
from
September 29, 2026 09:07
f806005 to
5555889
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SecurityLogger inherited from threading.local, but the stack of open loggers was a plain class attribute. Class attributes are not thread-local -- only the instance dict is -- so every thread in the process pushed and popped onto one shared list.
With concurrent requests this had two effects:
The stack now lives in a module level ContextVar,
security.logging.common.loggers_stack, holding an immutable tuple, which isolates it per thread and per asyncio task. A tuple is used so that the default value is never mutated in place and contexts cannot share a list by reference. The threading.local base class is dropped because the stack was the only reason for it.Breaking change:
SecurityLogger.loggersis removed. Read the stack withloggers_stack.get()instead, or useget_last_logger()to find a logger by name.