How to set log4net context property specific to an ASP.NET request

asp.nethttpcontextlog4netproperties

I've been using log4net to log our ASP.NET web site's log messages, and lately I wanted to add information about the page/handler where the error happened. I decided therefore to add the following line to Global.asax:

void Application_BeginRequest(object sender, EventArgs e)
{
    log4net.ThreadContext.Properties["page"] = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath;
}

and like wise I added %property{page} to my conversion pattern:

<conversionPattern value="%newline%date %-5level %property{page} - %message%newline%newline%newline" />

This worked fine for single requests. But then I noticed in my logs that the page property may change during an ASP.NET request. I have logging in one ASHX handler, and in the midst of its processing, the page property would change to a different value that points to an ASPX page. I concluded that there is another request coming to ASP.NET and its BeginRequest gets executed and the static page property in log4net.ThreadContext gets changed to another value.

Now, I would like to maintain the page property per request, so that I can have the path of the executing page logged to the log consistently. I tried to find an answer, but I came out with nothing. What is the recommended way to solve this problem? I'm sure this is very basic functionality of web server event logging.

Best Solution

Since ASP.NET does not guarantee that the entire page request will be processed on the same thread, I prefer getting the answer from HttpContext.Current as log4net processes the logging event.

The following GetCurrentPage class implements what the log4net manual calls an "Active Property Value" by overriding its ToString method:

public class GetCurrentPage
{
  public override string ToString()
  {
      if (null != HttpContext.Current)
      {
          return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath;
      }
      return string.Empty; // or "[No Page]" if you prefer
  }
}

Register this class in Global.asax's Application_Start in log4net's GlobalContext.

protected void Application_Start(object sender, EventArgs e)
{
    XmlConfigurator.Configure();
    GlobalContext.Properties["page"] = new GetCurrentPage();
}

When log4net writes the %property{page} part of the line it will call the ToString method of our GetCurrentPage class which will lookup the value in the current request.