Capture username with log4net
If the information that is available in the HttpContext is sufficient, that is, if the sample code you posted gives you the right answer (except for the MDC issue) and you would just rather just not write:
HttpContext context = HttpContext.Current;
if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
{
MDC.Set("user", HttpContext.Current.User.Identity.Name);
}
so often, then you might be able to add the username to your log "automatically" by writing your own custom PatternLayoutConverter for log4net. They are pretty easy to write and you can configure them in your log4net logging configuration just like the built in ones.
See this question for one example of how to write a custom PatternLayoutConverter:
Custom log4net property PatternLayoutConverter (with index)
Using the example at that link, you might be able to do something like this:
namespace Log4NetTest
{
class HttpContextUserPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
string name = "";
HttpContext context = HttpContext.Current;
if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
{
name = context.User.Identity.Name;
}
writer.Write(name);
}
}
}
You would configure this in log4net something like this:
//Log HttpContext.Current.User.Identity.Name
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p [User = %HTTPUser] %m%n"/>
<converter>
<name value="HTTPUser" />
<type value="Log4NetTest.HttpContextUserPatternConverter" />
</converter>
</layout>
In addition, you could build other pattern converters that use the Option parameter (see the example at the link above) to pull a specific item from the HttpContext.Current.Items or HttpContext.Current.Session collections.
Something like:
namespace Log4NetTest
{
class HttpContextSessionPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
//Use the value in Option as a key into HttpContext.Current.Session
string setting = "";
HttpContext context = HttpContext.Current;
if (context != null)
{
object sessionItem;
sessionItem = context.Session[Option];
if (sessionItem != null)
{
setting = sessionItem.ToString();
}
writer.Write(setting);
}
}
}
}
namespace Log4NetTest
{
class HttpContextItemPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
//Use the value in Option as a key into HttpContext.Current.Session
string setting = "";
HttpContext context = HttpContext.Current;
if (context != null)
{
object item;
item = context.Items[Option];
if (item != null)
{
setting = item.ToString();
}
writer.Write(setting);
}
}
}
}
You might also find these links useful:
http://piers7.blogspot.com/2005/12/log4net-context-problems-with-aspnet.html
Here, the blogger proposes a different solution to logging values from HttpContext than what I proposed. Read the blog post to see his description of the problem and for his solution. To summarize the solution, he stores an object in the GlobalDiagnosticContext (the more modern name for MDC). When log4net logs the value of the object it uses ToString(). the Implementation of his object retrieves a value from the HttpContext:
So, you might do something like this:
public class HttpContextUserNameProvider
{
public override string ToString()
{
HttpContext context = HttpContext.Current;
if (context != null && context.User != null && context.User.Identity.IsAuthenticated)
{
return context.Identity.Name;
}
return "";
}
}
You can put an instance of this object in the GlobalDiagnosticContext (MDC) early in your program and it will always return the right value since it is accessing HttpContext.Current.
MDC.Set("user", new HttpContextUserNameProvider());
This seems a lot easier than what I proposed!
For completeness, if someone wants to know how to do the same thing in NLog, NLog appears to make most/all of the HttpContext information available through its "aspnet-*" LayoutRenderers:
https://github.com/nlog/nlog/wiki/Layout-Renderers
How to conditionally capture user name with log4net layout pattern in ASP.NET web api 2?
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);
}
}
How to get current username instead of AppPool identity in a logfile with Log4Net
Replacing %username
by %identity
should do it. It is working for me in my current project.
You can learn more about log4net with this excellent tutorial
Windows username in the filename of the log4net output
The question should be: Is it possible to output environment variables, since you have USERNAME
and USERDOMAIN
.
And yes this should be possible with
<file type="log4net.Util.PatternString" value="%env{USERNAME}.log" />
or even
<file type="log4net.Util.PatternString" value="${USERNAME}.log" />
according to https://logging.apache.org/log4net/release/config-examples.html example with TMP
environment variable.
How to Log Client Ip, Browser Name and User Name using Log4net in asp.net?
To give you a more 'elegant' solution for this: use ndc.
<conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline" />
using Nested diagnostic context in code (NDC):
using(log4net.NDC.Push("["+GetIP() + " - " + HttpContext.Current.Request.Browser.Type + " - " + HttpContext.Current.Request.LogonUserIdentity.Name+"]")
{
//your code
//all log messages will contain the ndc string with the using.
}
Related Topics
How to Pass Values Between Forms in C# Windows Application
C# Split String and Remove Empty String
Expression.Lambda and Query Generation at Runtime, Simplest "Where" Example
ASP.NET Webapi: How to Perform a Multipart Post with File Upload Using Webapi Httpclient
How Does Task<Int> Become an Int
Why Is Try {...} Finally {...} Good; Try {...} Catch{} Bad
Replace Unicode Escape Sequences in a String
3D Relative Angle Sum Calculation
Extremely Large Single-Line File Parse
How to Group Windows Form Radio Buttons
Wpf C#: Rearrange Items in Listbox via Drag and Drop
.Net Regex Matching $ with the End of the String and Not of Line, Even with Multiline Enabled