Question

I have a Website that contains a number of webpages and some WCF services.

I have a logging IHttpModule which subscribes to PreRequestHandlerExecute and sets a number of log4net MDC variables such as:

MDC.Set("path", HttpContext.Current.Request.Path); 
string ip = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if(string.IsNullOrWhiteSpace(ip))
    ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
MDC.Set("ip", ip);

This module works well for my aspx pages.

To enable the module to work with WCF I have set aspNetCompatibilityEnabled="true" in the web.config and RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed on the service.

But when the service method is called the MDC no longer contains any of the set values. I have confirmed they are being set by putting a logging method in the PreRequestHandlerExecute.

I think the MDC is loosing the values because in the log I can see the PreRequestHandlerExecute handler method and service method calls are on separate threads.

The post log4net using ThreadContext.Properties in wcf PerSession service suggests using log4net.GlobalContext but I think that solution would run into issues if two users hit the application at the same time as GlobalContext is shared by all threads.

Is there a way to make this work?

Was it helpful?

Solution

Rather than taking the values from the HttpContext and storing them in one of log4net's context objects, why not log the values directly from the HttpContext? See my answer to the linked question for some techniques that might work for you.

Capture username with log4net

If you go to the bottom of my answer, you will find what might be the best solution. Write an HttpContext value provider object that you can put in log4net's GlobalDiagnosticContext.

For example, you might do something like this (untested)

public class HttpContextValueProvider
{
  private string name;
  public HttpContextValueProvider(string name)
  {
    this.name = name.ToLower();
  }

  public override string ToString()
  {
    if (HttpContext.Current == null) return "";

    var context = HttpContext.Current;

    switch (name)
    {
      case "path":
        return context.Request.Path;
      case "user"
        if (context.User != null && context.User.Identity.IsAuthenticated)
          return context.User.Identity.Name;
      case "ip":
        string ip = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
        if(string.IsNullOrWhiteSpace(ip))
          ip = context.Request.ServerVariables["REMOTE_ADDR"];
        return ip;
      default:
        return context.Items[name];
    }

    return "";
  }
}

In the default clause I assume the name, if it is not a specifically case that we want to handle, represents a value in the HttpContext.Current.Items dictionary. You could make it more generic by also adding the ability to access Request.ServerVariables and/or other HttpContext information.

You would use this object like so:

Somewhere in your program/web site/service, add some instances of the object to log4net's global dictionary. When log4net resolves the value from the dictionary, it will call ToString before logging the value.

GDC.Set("path", new HttpContextValueProvider("path"));
GDC.Set("ip", new HttpContextValueProvider("ip"));

Note, you are using log4net's global dictionary, but the objects that you are putting in the dictionary are essentially wrappers around the HttpContext.Current object, so you will always be getting the information for the current request, even if you are handling simultaneous requests. Good luck!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top