This is an automated email from the ASF dual-hosted git repository.

FreeAndNil pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git


The following commit(s) were added to refs/heads/master by this push:
     new 29a21046 Fix `LoggingEvent.UserName` resolving the Windows identity 
for every event (#304)
29a21046 is described below

commit 29a21046be37ce8b912237bac70d46c785de4e69
Author: Jan Friedrich <[email protected]>
AuthorDate: Tue Aug 4 20:49:30 2026 +0200

    Fix `LoggingEvent.UserName` resolving the Windows identity for every event 
(#304)
    
    The cache added in 2.0.15 (commit 9305ea9b) was declared as an instance 
field
    on `LoggingEvent`. Since every event is a fresh instance the guard was 
always
    false, so the `??=` never hit and each event ran a full
    `WindowsIdentity.GetCurrent().Name` - measured at 79.6 us on the machine 
used
    here. The same commit had changed `TryGetCurrentUserName` from static to
    instance, 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 
different
    objects 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:
    
    - A thread that is not impersonating runs as the process identity, so its 
name
      is resolved once per process. `WindowsIdentity.GetCurrent(ifImpersonating:
      true)` answers that question for 249 ns against 78 us for a name, making 
this
      the fast path for services, console applications and ASP.NET Core.
    - A thread that is impersonating - classic ASP.NET with
      `<identity impersonate="true"/>`, or `RunImpersonated` - has its name
      resolved once per distinct user, keyed by security identifier and capped 
at
      `MaxCachedUserNames` entries so that a site in front of a large directory
      cannot 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 `ImpersonationDoesNotSeedTheProcessUserName` guards against.
    
    A buffered `FixFlags.All` event 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 when `WindowsIdentity` is unusable, 
is
      also cached; it measured 33.8 us per call. Impersonation does not apply on
      that path.
    - The `SecurityException` handler now sets the unavailable flag, as the
      `PlatformNotSupportedException` handler already did. Under partial trust 
the
      old code threw, caught and logged once per event forever.
    
    Documentation: the `%username` pattern entry now explains the caching and
    points ASP.NET users at `%identity`, which is both cheaper and usually what 
is
    wanted because it reports the authenticated application user. The
    `BufferingForwardingAppender` manual page recommends `Partial` in its 
example
    instead of `All`; the surrounding comment already warned that the `All` 
default
    "may negatively impact performance enough to warrant changing it", and
    `LocationInfo` costs 6.9 us and 14 kB per event.
    
    `LogicalThreadContextProperties` no longer stores an empty dictionary just 
to
    replace 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.
---
 .../304-fix-username-resolved-for-every-event.xml  |  13 ++
 src/log4net.Tests/Core/UserNameFixingTest.cs       | 128 +++++++++++++++
 src/log4net/Core/LoggingEvent.cs                   | 173 +++++++++++++--------
 src/log4net/Layout/PatternLayout.cs                |  14 +-
 src/log4net/Util/LogicalThreadContextProperties.cs |  18 ++-
 src/log4net/Util/SystemInfo.cs                     |   5 +
 .../appenders/bufferingforwardingappender.adoc     |   4 +-
 7 files changed, 280 insertions(+), 75 deletions(-)

diff --git a/src/changelog/3.3.3/304-fix-username-resolved-for-every-event.xml 
b/src/changelog/3.3.3/304-fix-username-resolved-for-every-event.xml
new file mode 100644
index 00000000..c9253cf6
--- /dev/null
+++ b/src/changelog/3.3.3/304-fix-username-resolved-for-every-event.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="304" link="https://github.com/apache/logging-log4net/pull/304"/>
+  <description format="asciidoc">
+    fix `LoggingEvent.UserName` resolving the Windows identity for every 
event, because the cache
+ added in 2.0.15 was held in an instance field and so never applied. The 
process identity is now
+ resolved once, and impersonated identities once per user, cutting a buffered 
`FixFlags.All` event
+ from about 193 us to 17.5 us on the machine measured
+  </description>
+</entry>
diff --git a/src/log4net.Tests/Core/UserNameFixingTest.cs 
b/src/log4net.Tests/Core/UserNameFixingTest.cs
new file mode 100644
index 00000000..25dfe1ec
--- /dev/null
+++ b/src/log4net.Tests/Core/UserNameFixingTest.cs
@@ -0,0 +1,128 @@
+#region Apache License
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to you under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#endregion
+
+using System;
+using System.Reflection;
+using System.Security.Principal;
+
+using log4net.Core;
+
+using NUnit.Framework;
+
+namespace log4net.Tests.Core;
+
+/// <summary>
+/// Tests for <see cref="LoggingEvent.UserName"/>, whose name is resolved once 
for the process
+/// identity and once per impersonated user rather than once per logging event.
+/// </summary>
+[TestFixture]
+[Platform("Win")]
+[NonParallelizable]
+#if NET8_0_OR_GREATER
+[System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+public class UserNameFixingTest
+{
+  /// <summary>
+  /// The assumption the impersonation tests below rest on: running under a 
token - even the
+  /// process's own - is observable as impersonation.
+  /// </summary>
+  [Test]
+  public void RunImpersonatedIsObservableAsImpersonation()
+  {
+    using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+
+    bool impersonating = WindowsIdentity.RunImpersonated(identity.AccessToken, 
() =>
+    {
+      using WindowsIdentity? current = 
WindowsIdentity.GetCurrent(ifImpersonating: true);
+      return current is not null;
+    });
+
+    Assert.That(impersonating, Is.True);
+  }
+
+  /// <summary>
+  /// The UserName property matches the current Windows identity.
+  /// </summary>
+  [Test]
+  public void UserNameMatchesTheCurrentWindowsIdentity()
+  {
+    using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+
+    Assert.That(CreateEvent().UserName, Is.EqualTo(identity.Name));
+  }
+
+  /// <summary>
+  /// The UserName is stable across multiple events (cached, not resolved each 
time).
+  /// </summary>
+  [Test]
+  public void UserNameIsStableAcrossEvents()
+  {
+    string first = CreateEvent().UserName;
+
+    Assert.That(CreateEvent().UserName, Is.EqualTo(first));
+  }
+
+  /// <summary>
+  /// While impersonating, the UserName is correctly resolved to the 
impersonated user's identity.
+  /// </summary>
+  [Test]
+  public void UserNameIsResolvedWhileImpersonating()
+  {
+    using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+    string expected = identity.Name;
+
+    string actual = WindowsIdentity.RunImpersonated(
+      identity.AccessToken,
+      () => CreateEvent().UserName);
+
+    Assert.That(actual, Is.EqualTo(expected));
+  }
+
+  /// <summary>
+  /// The process identity name may only be resolved on a thread that is not 
impersonating.
+  /// Seeding it from an impersonating thread would report that user for every 
later event in
+  /// the process, including events raised on threads that impersonate nobody.
+  /// </summary>
+  [Test]
+  public void ImpersonationDoesNotSeedTheProcessUserName()
+  {
+    FieldInfo field = typeof(LoggingEvent).GetField(
+        "_processUserName",
+        BindingFlags.Static | BindingFlags.NonPublic)
+      ?? throw new InvalidOperationException("LoggingEvent._processUserName is 
missing");
+    object? saved = field.GetValue(null);
+    try
+    {
+      field.SetValue(null, null);
+      using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+
+      WindowsIdentity.RunImpersonated(identity.AccessToken, () => 
CreateEvent().UserName);
+
+      Assert.That(field.GetValue(null), Is.Null);
+    }
+    finally
+    {
+      field.SetValue(null, saved);
+    }
+  }
+
+  private static LoggingEvent CreateEvent()
+    => new(typeof(UserNameFixingTest), null, "UserNameFixingTest", Level.Info, 
"message", null);
+}
diff --git a/src/log4net/Core/LoggingEvent.cs b/src/log4net/Core/LoggingEvent.cs
index d915f69c..42aae569 100644
--- a/src/log4net/Core/LoggingEvent.cs
+++ b/src/log4net/Core/LoggingEvent.cs
@@ -1,4 +1,4 @@
-#region Apache License
+#region Apache License
 //
 // Licensed to the Apache Software Foundation (ASF) under one or more 
 // contributor license agreements. See the NOTICE file distributed with
@@ -18,6 +18,7 @@
 #endregion
 
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
@@ -701,83 +702,84 @@ private static string ReviseThreadName(string? threadName)
   /// </value>
   /// <remarks>
   /// <para>
-  /// On Windows it calls <c>WindowsIdentity.GetCurrent().Name</c> to get the 
name of
-  /// the current windows user. On other OSes it calls Environment.UserName.
+  /// On Windows this resolves the name from <see cref="WindowsIdentity"/>, on 
other platforms
+  /// from <see cref="Environment.UserName"/>.
   /// </para>
   /// <para>
-  /// To improve performance, we could cache the string representation of 
-  /// the name, and reuse that as long as the identity stayed constant.  
-  /// Once the identity changed, we would need to re-assign and re-render 
-  /// the string.
+  /// Resolving the name is by far the most expensive part: obtaining the 
identity costs a few
+  /// hundred nanoseconds, while translating it into a <c>DOMAIN\user</c> 
string is a local
+  /// security authority lookup costing tens of microseconds. The name is 
therefore cached, in a
+  /// way that still reports the right user in a process which switches users:
   /// </para>
-  /// <para>
-  /// However, the <c>WindowsIdentity.GetCurrent()</c> call seems to 
-  /// return different objects every time, so the current implementation 
-  /// doesn't do this type of caching.
-  /// </para>
-  /// <para>
-  /// Timing for these operations:
-  /// </para>
-  /// <list type="table">
-  ///   <listheader>
-  ///     <term>Method</term>
-  ///     <description>Results</description>
-  ///   </listheader>
-  ///   <item>
-  ///      <term><c>WindowsIdentity.GetCurrent()</c></term>
-  ///      <description>10000 loops, 00:00:00.2031250 seconds</description>
-  ///   </item>
-  ///   <item>
-  ///      <term><c>WindowsIdentity.GetCurrent().Name</c></term>
-  ///      <description>10000 loops, 00:00:08.0468750 seconds</description>
-  ///   </item>
+  /// <list type="bullet">
+  ///   <item><description>
+  ///     A thread that is not impersonating runs as the process identity, so 
its name is
+  ///     resolved once per process. Asking whether the thread impersonates, 
via
+  ///     <see cref="WindowsIdentity.GetCurrent(bool)"/>, is around 300 times 
cheaper than
+  ///     resolving a name, so this is the fast path for services, console 
applications and
+  ///     ASP.NET Core.
+  ///   </description></item>
+  ///   <item><description>
+  ///     A thread that is impersonating - classic ASP.NET with
+  ///     <c>&lt;identity impersonate="true"/&gt;</c>, or 
<c>WindowsIdentity.RunImpersonated</c> -
+  ///     has its name resolved once per distinct user and cached by security 
identifier, for up
+  ///     to <see cref="MaxCachedUserNames"/> users. Past that bound the name 
is resolved per
+  ///     event rather than letting the cache grow without limit.
+  ///   </description></item>
   /// </list>
   /// <para>
-  /// This means we could speed things up almost 40 times by caching the 
-  /// value of the <c>WindowsIdentity.GetCurrent().Name</c> property, since 
-  /// this takes (8.04-0.20) = 7.84375 seconds.
+  /// In classic ASP.NET, <see cref="Identity"/> is both cheaper than this 
property and usually
+  /// what the application actually wants, because it reports the 
authenticated application user
+  /// rather than the Windows account the request happens to run as.
   /// </para>
   /// </remarks>
   public string UserName =>
       _data.UserName ??= TryGetCurrentUserName() ?? 
SystemInfo.NotAvailableText;
 
-  private string? TryGetCurrentUserName()
+  private static string? TryGetCurrentUserName()
   {
     try
     {
-      if (_platformDoesNotSupportWindowsIdentity)
+      if (_windowsIdentityUnavailable)
       {
-        // we've already received one PlatformNotSupportedException or null 
from TryReadWindowsIdentityUserName
-        // and it's highly unlikely that will change
-        return Environment.UserName;
+        // we've already seen a PlatformNotSupportedException, a 
SecurityException or a
+        // non-Windows platform, and it's highly unlikely that will change
+        return CachedEnvironmentUserName;
       }
-    
-      if (_cachedWindowsIdentityUserName is not null)
+
+      if (!IsWindowsIdentitySupported())
       {
-        return _cachedWindowsIdentityUserName;
+        _windowsIdentityUnavailable = true;
+        return CachedEnvironmentUserName;
       }
-      if (TryReadWindowsIdentityUserName() is string userName)
+
+      using WindowsIdentity? impersonated = 
WindowsIdentity.GetCurrent(ifImpersonating: true);
+      if (impersonated is null)
       {
-        _cachedWindowsIdentityUserName = userName;
-        return _cachedWindowsIdentityUserName;
+        // Not impersonating, so this thread runs as the process identity. 
Reading it through
+        // GetCurrent() is only correct here, which is why the field is 
assigned nowhere else:
+        // seeding it from an impersonating thread would report that user for 
the whole process.
+        return _processUserName ??= ReadProcessUserName();
       }
-      _platformDoesNotSupportWindowsIdentity = true;
-      return Environment.UserName;
+
+      return ReadImpersonatedUserName(impersonated);
     }
     catch (PlatformNotSupportedException)
     {
-      _platformDoesNotSupportWindowsIdentity = true;
-      return Environment.UserName;
+      _windowsIdentityUnavailable = true;
+      return CachedEnvironmentUserName;
     }
     catch (SecurityException)
     {
-      // This security exception will occur if the caller does not have 
-      // some undefined set of SecurityPermission flags.
+      // This security exception will occur if the caller does not have
+      // some undefined set of SecurityPermission flags. It will keep 
happening, so remember it
+      // instead of throwing and catching once per logging event.
+      _windowsIdentityUnavailable = true;
       LogLog.Debug(
           _declaringType,
           "Security exception while trying to get current windows identity. 
Error Ignored."
       );
-      return Environment.UserName;
+      return CachedEnvironmentUserName;
     }
     catch (Exception e) when (!e.IsFatal())
     {
@@ -785,29 +787,47 @@ private static string ReviseThreadName(string? threadName)
     }
   }
 
-  private string? _cachedWindowsIdentityUserName;
-  
-  /// <returns>
-  ///  On Windows: UserName in case of success, empty string for unexpected 
null in identity or Name
-  ///  <para/>
-  ///  On other OSes: null
-  /// </returns>
-  /// <exception cref="PlatformNotSupportedException">Thrown on non-Windows 
platforms on net462</exception>
-  private static string? TryReadWindowsIdentityUserName()
+  /// <returns><see langword="false"/> on platforms where <see 
cref="WindowsIdentity"/> cannot be used</returns>
+  private static bool IsWindowsIdentitySupported()
   {
     // According to docs RuntimeInformation.IsOSPlatform is supported from 
netstandard1.1,
     // but it's erroring in runtime on < net471
 #if NET471_OR_GREATER || NETSTANDARD2_0_OR_GREATER
-    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-    {
-      return null;
-    }
+    return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+#else
+    return !SystemInfo.IsMono;
 #endif
+  }
+
+  /// <returns>UserName of the process identity, empty string for an 
unexpected null in identity or Name</returns>
+  /// <exception cref="PlatformNotSupportedException">Thrown on non-Windows 
platforms on net462</exception>
+  private static string ReadProcessUserName()
+  {
     using WindowsIdentity identity = WindowsIdentity.GetCurrent();
     return identity?.Name ?? string.Empty;
   }
 
-  private static bool _platformDoesNotSupportWindowsIdentity;
+  /// <returns>UserName of <paramref name="identity"/>, resolved once per 
security identifier</returns>
+  private static string ReadImpersonatedUserName(WindowsIdentity identity)
+  {
+    if (identity.User is not SecurityIdentifier sid)
+    {
+      return identity.Name ?? string.Empty;
+    }
+
+    if (_userNamesBySid.TryGetValue(sid, out string? cached))
+    {
+      return cached;
+    }
+
+    string userName = identity.Name ?? string.Empty;
+    if (_userNamesBySid.Count < MaxCachedUserNames)
+    {
+      _userNamesBySid[sid] = userName;
+    }
+
+    return userName;
+  }
 
   /// <summary>
   /// Gets the identity of the current thread principal.
@@ -1293,6 +1313,33 @@ public PropertiesDictionary GetProperties()
     return _compositeProperties!.Flatten();
   }
 
+  /// <summary>
+  /// Upper bound on <see cref="_userNamesBySid"/>, so that a process 
impersonating an unbounded
+  /// set of users - an intranet site in front of a large directory - does not 
accumulate one
+  /// cache entry per visitor.
+  /// </summary>
+  private const int MaxCachedUserNames = 64;
+
+  private static string? _cachedEnvironmentUserName;
+
+  /// <summary>
+  /// <see cref="Environment.UserName"/>, resolved once per process. Only 
reached when
+  /// <see cref="WindowsIdentity"/> is unusable, where thread level 
impersonation does not apply.
+  /// </summary>
+  private static string CachedEnvironmentUserName => 
_cachedEnvironmentUserName ??= Environment.UserName;
+
+  /// <summary>
+  /// Name of the process identity, resolved once on a thread that is not 
impersonating.
+  /// </summary>
+  private static string? _processUserName;
+
+  /// <summary>
+  /// Names of impersonated users, keyed by security identifier.
+  /// </summary>
+  private static readonly ConcurrentDictionary<SecurityIdentifier, string> 
_userNamesBySid = new();
+
+  private static bool _windowsIdentityUnavailable;
+
   /// <summary>
   /// The internal logging event data.
   /// </summary>
diff --git a/src/log4net/Layout/PatternLayout.cs 
b/src/log4net/Layout/PatternLayout.cs
index 7f8b2dca..29e64746 100644
--- a/src/log4net/Layout/PatternLayout.cs
+++ b/src/log4net/Layout/PatternLayout.cs
@@ -556,13 +556,21 @@ namespace log4net.Layout;
 ///        </para>
 ///        <para>
 ///        <b>WARNING</b> Generating caller WindowsIdentity information is
-///        extremely slow. Its use should be avoided unless execution speed
-///        is not an issue.
+///        slow. The name is cached per identity, so a process that does not
+///        impersonate pays for it once, and one that impersonates pays once
+///        per distinct user - but the first event for each user is expensive.
+///        </para>
+///        <para>
+///        In classic ASP.NET with <c>&lt;identity impersonate="true"/&gt;</c> 
this reports the
+///        Windows account the request runs as. <b>identity</b> is both 
cheaper and usually what
+///        is wanted there, because it reports the authenticated application 
user. On ASP.NET Core
+///        there is no impersonation by default, so this reports the 
application pool identity
+///        rather than the request user.
 ///        </para>
 ///      </description>
 ///    </item>
 ///     <item>
-///      <term>utcdate</term> 
+///      <term>utcdate</term>
 ///      <description>
 ///       <para>
 ///       Used to output the date of the logging event in universal time. 
diff --git a/src/log4net/Util/LogicalThreadContextProperties.cs 
b/src/log4net/Util/LogicalThreadContextProperties.cs
index 7b4081c3..4e089178 100644
--- a/src/log4net/Util/LogicalThreadContextProperties.cs
+++ b/src/log4net/Util/LogicalThreadContextProperties.cs
@@ -78,14 +78,14 @@ internal LogicalThreadContextProperties()
     }
     set
     {
-      // Force the dictionary to be created
-      PropertiesDictionary props = GetProperties(true)!;
       // Reason for cloning the dictionary below: object instances set on the 
CallContext
-      // need to be immutable to correctly flow through async/await
-      PropertiesDictionary immutableProps = new(props)
-      {
-        [key] = value
-      };
+      // need to be immutable to correctly flow through async/await.
+      // The existing dictionary is read without creating one, because the 
clone replaces it
+      // anyway - asking for creation would store an empty dictionary just to 
overwrite it.
+      PropertiesDictionary immutableProps = GetProperties(false) is 
PropertiesDictionary props
+        ? new(props)
+        : [];
+      immutableProps[key] = value;
       SetLogicalProperties(immutableProps);
     }
   }
@@ -101,7 +101,9 @@ internal LogicalThreadContextProperties()
   /// </remarks>
   public void Remove(string key)
   {
-    if (GetProperties(false) is PropertiesDictionary dictionary)
+    // Cloning is only worthwhile when the key is actually present - otherwise 
the clone would
+    // replace the stored dictionary with an equal one.
+    if (GetProperties(false) is PropertiesDictionary dictionary && 
dictionary.Contains(key))
     {
       PropertiesDictionary immutableProps = new(dictionary);
       immutableProps.Remove(key);
diff --git a/src/log4net/Util/SystemInfo.cs b/src/log4net/Util/SystemInfo.cs
index 74f0f6be..d5b9f9d3 100644
--- a/src/log4net/Util/SystemInfo.cs
+++ b/src/log4net/Util/SystemInfo.cs
@@ -41,6 +41,11 @@ public static class SystemInfo
   /// </summary>
   internal static bool IsAndroid { get; } = IsAndroidCore();
 
+  /// <summary>
+  /// Is the mono runtime used
+  /// </summary>
+  internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") is not 
null;
+  
   /// <summary>
   /// Initialize default values for private static fields.
   /// </summary>
diff --git 
a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/bufferingforwardingappender.adoc
 
b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/bufferingforwardingappender.adoc
index 6775c917..f46c981a 100644
--- 
a/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/bufferingforwardingappender.adoc
+++ 
b/src/site/antora/modules/ROOT/pages/manual/configuration/appenders/bufferingforwardingappender.adoc
@@ -31,9 +31,11 @@ The following example shows how to configure the 
`BufferingForwardingAppender` t
   <!--
     The value configures what gets fixed immediately when calling logger.Log().
     The default value is All, which may negatively impact performance enough 
to warrant changing it to fix less data.
+    Partial is the recommended starting point: it fixes the message, thread 
name, exception, domain
+    and properties, and leaves out LocationInfo, which has to walk the call 
stack for every event.
     More information can be found at 
https://github.com/apache/logging-log4net/blob/master/src/log4net/Core/FixFlags.cs
   -->
-  <fix value="All"/>
+  <fix value="Partial"/>
   <appender-ref ref="ConsoleAppender" />
 </appender>
 ----

Reply via email to