如何在 ASP.NET web api 2 中使用 log4net 布局模式有条件地捕获用户名?

How to conditionally capture user name with log4net layout pattern in ASP.NET web api 2?

我有一个 Web api 2 应用程序,目前已配置 Windows 身份验证。该应用程序有一个名为 FlowManager 运行 使用 ApplicationPoolIdentity 的专用应用程序池。

这里是来自 web.config 文件的 log4net 配置,我在 PatternLayout 中使用 %username 来记录用户名。

<log4net>
    <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="C:\Logs\FlowManager\FlowManager.log" />
      <param name="AppendToFile" value="true" />
      <datePattern value="yyyyMMdd" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="3" />
      <maximumFileSize value="2MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%d{MM-dd-yyyy HH:mm:ss:fff} %-5level [%property{log4net:HostName}, %username] %m%n" />
      </layout>
    </appender>
    <root>
      <level value="TRACE" />
      <appender-ref ref="LogFileAppender" />
    </root>
  </log4net>

所以在我要登录的应用程序代码中,我正在使用

logger.Debug("Request entered"); //in a http module

logger.Debug("Test log entry");  //in the api controller

正在按预期生成日志。

06-08-2016 12:06:07:287 DEBUG [1178959001DLP, IIS APPPOOL\FlowManager] Request entered

06-08-2016 12:06:09:257 DEBUG [1178959001DLP, IIS APPPOOL\FlowManager] Test log entry

现在我被要求记录经过身份验证的用户名。我发现了这个很好的问题 Capture username with log4net 并按照说明进行操作并能够实现它。这是新的 log4net 配置的样子。为简单起见,我只展示 log4net 的 PatternLayout 部分。

<layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%d{MM-dd-yyyy HH:mm:ss:fff} %-5level [%property{log4net:HostName}, %HTTPUser] %m%n" />
        <converter>
          <name value="HTTPUser" />
          <type value="LoggerExtentions.ContextUserPatternConverter" />
        </converter>
      </layout>

如您所见,我创建了一个自定义模式转换器来捕获用户名。

using System.Threading;
using System.Web;
using log4net.Core;
using log4net.Layout.Pattern;

namespace LoggerExtentions
{
    /// <summary>
    /// Log4net custom pattern converter to log context user instaed of app pool user
    /// </summary>
    public class ContextUserPatternConverter : PatternLayoutConverter
    {
        protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
        {
            var userName = string.Empty;
            var context = HttpContext.Current;
            if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
            {
                userName = context.User.Identity.Name;
            }
            else
            {
                var threadPincipal = Thread.CurrentPrincipal;
                if (threadPincipal != null && threadPincipal.Identity.IsAuthenticated)
                {
                    userName = threadPincipal.Identity.Name;
                }
            }
            writer.Write(userName);
        }
    }
}

这给了我以下日志消息

06-08-2016 13:06:09:123 DEBUG [1178959001DLP, ] Request entered

06-08-2016 13:06:09:257 DEBUG [1178959001DLP, DOMAIN\johnkl] Test log entry

如果您注意到从 http 模块创建的日志条目没有用户名,我知道这是因为尚未为经过身份验证的用户设置上下文。在这种情况下,我被要求记录 log4net 捕获的默认用户名。我的问题是我怎样才能实现它?每当我没有 HttpContext 用户时,如何提供 log4net 捕获的默认用户名?有没有办法在 log4net Pattern 布局中提供条件语句?或者任何其他替代选项?

看来我找到了另一种捕获它的方法。由于 log4net %username 默认为应用程序池身份,我修改了自定义模式转换器以回退到应用程序池身份。

更新: 发现 loggingEvent 参数具有用户名 属性。我修改了我的代码以使用它而不是直接调用 System.Security.Principal.WindowsIdentity.GetCurrent().Name 。 LoggingEvent.UserName 内部调用相同,但我决定使用 loggingEvent.UserName 以防将来 log4net 实现修改其行为。

public class ContextUserPatternConverter : PatternLayoutConverter
    {
        protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
        {
            var userName = string.Empty;
            var context = HttpContext.Current;
            if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
            {
                userName = context.User.Identity.Name;
            }
            else
            {
                var threadPincipal = Thread.CurrentPrincipal;
                if (threadPincipal != null && threadPincipal.Identity.IsAuthenticated)
                {
                    userName = threadPincipal.Identity.Name;
                }
            }
            if (string.IsNullOrEmpty(userName))
            {
                userName = loggingEvent.UserName;
                //get app pool identity
                //userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
            }
            writer.Write(userName);
        }
    }