Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Log4Net, ThreadContext, and Global.asax

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

like image 319
tribus Avatar asked Jun 30 '09 21:06

tribus


People also ask

How to use log4net in. net 6?

Just create a log4net. config file with a log file as an appender, then add two using statements and a single line of code to the new . NET 6 hosting model: //Program.

What is the use of context properties in log4net?

Context Properties The log4net contexts store properties, i.e. name value pairs. The name is a string the value is any object. A property can be set as follows: log4net.


2 Answers

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.

like image 88
Ben Scheirman Avatar answered Sep 25 '22 12:09

Ben Scheirman


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.

like image 29
Shane K Avatar answered Sep 25 '22 12:09

Shane K