5

I would like to use the following in my wcf service to log the user in the log message:

log4net.ThreadContext.Properties["user"] = this.currentUser.LoginName;

I have the service set up to run in InstanceContextMode.PerSession. In the initial call to the wcf service I am setting this ThreadContext property to the current user that is logged in but each subsequent call does not log this property.

I'm pretty sure that for each call to the service it's running the task on a different thread even though it's set to use PerSession. I assume it's using a thread pool to process the requests.

Is there a way to set this so that I don't have to do it in every wcf method?

Maxim
  • 7,268
  • 1
  • 32
  • 44
Cole W
  • 15,123
  • 6
  • 51
  • 85
  • Better not doing so because WCF uses the ThreadPool so it won't work as you stated, moreover you set this property on a thread that might be used for different task later. – Maxim Jan 28 '12 at 20:29
  • You can implement your own SynchronizationContext and keep the same thread per session. But I think it's much more simple to set the user for every service call(Using LogicalThreadContext, start in the beginning of every call and close in the end). – Maxim Jan 28 '12 at 20:39

2 Answers2

2

I ran into the same problem and this is how I got it to work. You can use GlobalContext since it will be evaluated for each call anyway.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
    //static constructor
    static MyService()
    {
        log4net.Config.XmlConfigurator.Configure();
        log4net.GlobalContext.Properties["user"] = new UserLogHelper();
    }
    ...
}

Then you have to define a simple class:

private class UserLogHelper
{
    public override string ToString()
    {
        var instanceContext = OperationContext.Current.InstanceContext;
        var myServiceInstance = instanceContext.GetServiceInstance() as MyService;
        return myServiceInstance?.currentUser?.LoginName;
   }
}
AlexDev
  • 4,049
  • 31
  • 36
  • Awesome. I'm going to take a look at implementing this as well. – Cole W Mar 06 '14 at 18:19
  • This worked. Although I still have a problem with some things that run asynchronous within my Wcf method call that may log things after the `OperationContext.Current.InstanceContext` goes out of scope but that is of no fault of this solution. – Cole W Sep 25 '15 at 13:37
1

Log4net supports "calculated context values". By using this you could write a class like this:

public class UserNameContext
{
    public override string ToString()
    {
        string userName = ...; // get the name of the current user

        return userName;
    }
}

If you add this to the global context you can access the property in your appenders (like you are used to). The 'ToString' method will be executed every time and thus you get the correct user name.

More on context values can be found in this great tutorial: http://www.beefycode.com/post/Log4Net-Tutorial-pt-6-Log-Event-Context.aspx

Stefan Egli
  • 17,398
  • 3
  • 54
  • 75
  • I'm not sure the global context will work in my case since it's a single wcf service hosting multiple clients. This global context would get overwritten as each user connects to the service. Maybe I'm not envisioning this solution correctly. If I'm not I'd like to hear your thoughts. – Cole W Feb 15 '12 at 01:25
  • There is only one instance of this class. The ToString() method is responsible for getting the current user i.e. the user "related" to the current request. One way to do this would be to use the Thread.CurrentPrincipal but I am not sure if this applies to your situation. – Stefan Egli Feb 15 '12 at 18:20