Fix LoggingEvent.UserName resolving the Windows identity for every event - #304
Merged
Conversation
Contributor
|
@FreeAndNil can we adjust the tests? |
Contributor
Author
@gdziadkiewicz Yes, we can ;-) |
gdziadkiewicz
approved these changes
Aug 4, 2026
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.
The cache added in 2.0.15 (commit 9305ea9) was declared as an instance field
on
LoggingEvent. Since every event is a fresh instance the guard was alwaysfalse, so the
??=never hit and each event ran a fullWindowsIdentity.GetCurrent().Name- measured at 79.6 us on the machine usedhere. The same commit had changed
TryGetCurrentUserNamefrom static toinstance, so the caching it introduced has never taken effect.
The doc comment above the property already described the intended design and
the reason it was abandoned: the name should be cached "as long as the identity
stayed constant", but
WindowsIdentity.GetCurrent()"seems to return differentobjects every time". Object identity was the wrong comparison. The security
identifier is stable, and obtaining the identity is far cheaper than resolving
its name - the timing table in that same comment puts the two at roughly 20 ns
against 804 ns.
So the name is now cached without changing what the property reports:
is resolved once per process.
WindowsIdentity.GetCurrent(ifImpersonating: true)answers that question for 249 ns against 78 us for a name, making thisthe fast path for services, console applications and ASP.NET Core.
<identity impersonate="true"/>, orRunImpersonated- has its nameresolved once per distinct user, keyed by security identifier and capped at
MaxCachedUserNamesentries so that a site in front of a large directorycannot accumulate one entry per visitor.
The process-identity value is assigned in exactly one place, inside the branch
that has already established the thread is not impersonating. Seeding it from
an impersonating thread would report that user for the rest of the process,
which
ImpersonationDoesNotSeedTheProcessUserNameguards against.A buffered
FixFlags.Allevent goes from 192,937 to 17,578 ns on this machine,both sides built as netstandard2.0. The remainder is
FixFlags.LocationInfo,which is untouched here.
Two related changes in the same method:
Environment.UserName, the fallback whenWindowsIdentityis unusable, isalso cached; it measured 33.8 us per call. Impersonation does not apply on
that path.
SecurityExceptionhandler now sets the unavailable flag, as thePlatformNotSupportedExceptionhandler already did. Under partial trust theold code threw, caught and logged once per event forever.
Documentation: the
%usernamepattern entry now explains the caching andpoints ASP.NET users at
%identity, which is both cheaper and usually what iswanted because it reports the authenticated application user. The
BufferingForwardingAppendermanual page recommendsPartialin its exampleinstead of
All; the surrounding comment already warned that theAlldefault"may negatively impact performance enough to warrant changing it", and
LocationInfocosts 6.9 us and 14 kB per event.LogicalThreadContextPropertiesno longer stores an empty dictionary just toreplace it on the next line, and no longer clones on removal of an absent key.
Both are cleanups; neither changes the allocation profile of the common
set/remove pattern.