0

I have a web api 2 application, as of now configured with Windows Authentication. The application has a dedicated app pool called FlowManager running using ApplicationPoolIdentity.

here is the log4net configuration from web.config file, where I am using %username in PatternLayout to log the user name.

<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>

So in the application code where I want to log, I am using

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

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

It is generating log as expected.

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

Now I was asked to log the authenticated user name. I found this nice question Capture username with log4net and followed the instructions and was able to achieve it. Here is how the new log4net configuration looks like. For simplicity, I am just showing PatternLayout section of log4net.

<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>

As you see I created a custom pattern converter to capture user name.

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);
        }
    }
}

This gives me the following log messages

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

If you notice the log entry that is created from http module has no user name, which I get it because Context is not yet set with authenticated user. I have been asked to log the default username captured by log4net in such situations. My question is how can I achieve it? Whenever I don't have a HttpContext user, how can I supply the default username captured by log4net? Is there a way to provide a conditional statement in the log4net Pattern layout? Or any other alternate option?

Community
  • 1
  • 1
Vinod
  • 1,882
  • 2
  • 17
  • 27

1 Answers1

0

Looks like I was able to find an alternate way to capture it. As log4net %username defaults to app pool identity, I modified the custom pattern converter to fallback to app pool identity.

UPDATE: Found that loggingEvent parameter has the UserName property. I have modified my code to use that instead of calling System.Security.Principal.WindowsIdentity.GetCurrent().Name directly. The LoggingEvent.UserName internally calls the same, but I decided to make use of loggingEvent.UserName just in case if log4net implementation modifies its behavior in future.

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);
        }
    }
Vinod
  • 1,882
  • 2
  • 17
  • 27
  • If the authentication mechanism always sets both HttpContext.Current.User and Thread.CurrentPrincipal you can just look at Thread.CurrentPrincipal instead of depending on both. That way if you make it a reusable library, you don't have to depend on System.web anymore. – Vinod Jul 08 '16 at 14:28
  • try %identity instead – Larry Flewwelling Aug 07 '19 at 17:08