I am working on a Log4Net configuration that will log all unhandled exceptions. I need certain properties, based on user, to be added to each log entry. I have set this up successfully in the following manner in my Application_Error event. Here is my complete global.asax
Imports log4net
Imports log4net.Config
Public Class Global_asax
Inherits System.Web.HttpApplication
'Define a static logger variable
Private Shared log As ILog = LogManager.GetLogger(GetType(Global_asax))
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the application is started
ConfigureLogging()
End Sub
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when an unhandled error occurs
Dim ex As Exception = Server.GetLastError()
ThreadContext.Properties("user") = User.Identity.Name
ThreadContext.Properties("appbrowser") = String.Concat(Request.Browser.Browser, " ", Request.Browser.Version)
If TypeOf ex Is HttpUnhandledException AndAlso ex.InnerException IsNot Nothing Then
ex = ex.InnerException
End If
log.Error(ex)
ThreadContext.Properties.Clear()
End Sub
Private Sub ConfigureLogging()
Dim logFile As String = Server.MapPath("~/Log4Net.config")
log4net.Config.XmlConfigurator.ConfigureAndWatch(New System.IO.FileInfo(logFile))
log4net.GlobalContext.Properties("appname") = System.Reflection.Assembly.GetExecutingAssembly.GetName.Name
End Sub
End Class
This appears to be working fine. However, I have some questions that I am unable to answer.
Is the way that I am adding the user specific properties, via the threadcontext, correct? Will this always log the correct information, even under load? When would you use threadlogicalcontext? Is there a better way to do this?
Thanks
It is not safe to load request-specific values into ThreadContext
like that. The reason is that ASP.NET shared threads to service requests. It does this quite often, in fact.
You could instead use LogicalThreadContext
, however that simply stores the values in Call Context, which is used for Remoting.
AFAIK there is no HttpContext specific context storage, so what you can do is instead assign a "value provider" instance as your thread context, and at runtime it will call .ToString() on this class to get the value.
public class HttpContextUserProvider
{
public override string ToString()
{
return HttpContext.Current.User.Identity.Name;
}
}
It's less than ideal, but it works.
Ben's answer is right on.
However, like some of the other users, I was still a bit lost on how to proceed. This log4net Context problems with ASP.Net thread agility post and especially this Marek Stój's Blog - log4net Contextual Properties and ASP.NET one give some more context for the problem with some excellent code examples.
I highly recommend Marek Stój's implementation, although the ThreadContext.Properties["UserName"]
needed to be replaced with ThreadContext.Properties["User"]
in my case.
I added a BeginRequest method to my Logger class which I call from Application_AuthenticateRequest which loads all the relevant log4net properties.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
Logger.BeginRequest(Request);
}
And the method code:
public static void BeginRequest(System.Web.HttpRequest request)
{
if (request == null) return;
ThreadContext.Properties["ip_address"] = AdaptivePropertyProvider.Create("ip_address", IPNetworking.GetMachineNameAndIP4Address());
ThreadContext.Properties["rawUrl"] = AdaptivePropertyProvider.Create("rawUrl", request.RawUrl);
if (request.Browser != null && request.Browser.Capabilities != null)
ThreadContext.Properties["browser"] = AdaptivePropertyProvider.Create("browser", request.Browser.Capabilities[""].ToString());
if (request.IsAuthenticated && HttpContext.Current.User != null)
ThreadContext.Properties["User"] = AdaptivePropertyProvider.Create("user", HttpContext.Current.User.Identity.Name);
}
I found I had to pass in the Request object instead of using HttpContext.Current.Request
within the method. Otherwise I would loose the user and authentication information. Note that the IPNetworking
class is my own so you will need to provide your own method of obtaining the client IP. The AdaptivePropertyProvider
class is directly from Marek Stój.