I have either an error in log4net, or a misunderstanding on my part.
I am trying to use a LogicalThreadContext to associate some data with a call context and propagate it to any log entries created by any thread in this context. This is an implied advantage of LogicalThreadContext over ThreadContext .
I was not able to get the distribution to work, so I put together a simple unit test to see if it would work, but itβs not. Here he is:
[Fact] public void log4net_logical_thread_context_test() { XmlConfigurator.Configure(); var log = LogManager.GetLogger(GetType()); var waitHandle = new ManualResetEvent(false); using (LogicalThreadContext.Stacks["foo"].Push("Some contextual info")) { log.Debug("START"); ThreadPool.QueueUserWorkItem(delegate { log.Debug("A DIFFERENT THREAD"); waitHandle.Set(); }); waitHandle.WaitOne(); log.Debug("STOP"); } }
My log4net configuration looks like this:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> </configSections> <log4net> <appender name="FileAppender" type="log4net.Appender.FileAppender"> <file value="log.txt" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="[%thread]|[%property{foo}]|%message%newline"/> </layout> </appender> <root> <level value="DEBUG" /> <appender-ref ref="FileAppender" /> </root> </log4net> </configuration>
And my conclusion is as follows:
[xUnit.net STA Test Execution Thread]|[Some contextual info]|START [32]|[(null)]|A DIFFERENT THREAD [xUnit.net STA Test Execution Thread]|[Some contextual info]|STOP
As you can see, the data that I click on the LTC stack is only present in the logging statements executed on the same thread. The journal statement made by the background thread is missing context data. Debugging through the test, I could see that, indeed, LogicalThreadContext.Stacks.Count is zero in the background thread.
Delving into the log4net source, I found it using the CallContext class. This class does what it says in tin - it allows the context for the current "call" to be stored and retrieved. How this is done at a low level, I have no idea.
CallContext has two sets of methods with which you can save and retrieve contextual information: GetData / SetData and LogicalGetData / LogicalSetData . The documentation is very easy in detail regarding the difference between the two sets of methods, but the examples use GetData / SetData . And log4net LogicalThreadContext .
A quick test showed that GetData / SetData exhibits the same problem - data is not propagated through streams. I thought I would give LogicalGetData / LogicalSetData a go instead:
[Fact] public void call_context_test() { XmlConfigurator.Configure(); var log = LogManager.GetLogger(GetType()); var count = 5; var waitHandles = new ManualResetEvent[count]; for (var i = 0; i < count; ++i) { waitHandles[i] = new ManualResetEvent(false); var localI = i; // on a bg thread, set some call context data ThreadPool.QueueUserWorkItem(delegate { CallContext.LogicalSetData("name", "value " + localI); log.DebugFormat("Set call context data to '{0}'", CallContext.LogicalGetData("name")); var localWaitHandle = new ManualResetEvent(false); // then on another bg thread, make sure the logical call context value is correct with respect to the "owning" bg thread ThreadPool.QueueUserWorkItem(delegate { var value = CallContext.LogicalGetData("name"); log.DebugFormat("Retrieved call context data '{0}'", value); Assert.Equal("value " + localI, value); localWaitHandle.Set(); }); localWaitHandle.WaitOne(); waitHandles[localI].Set(); }); } foreach (var waitHandle in waitHandles) { waitHandle.WaitOne(); } }
This test passes - contextual information is successfully distributed across streams when using LogicalGetData / LogicalSetData .
So my question is this: does log4net have this wrong? Or am I missing something?
UPDATE: I also tried to do a custom build of log4net with my LogicalThreadContextProperties , modified according to my findings above. I restarted my initial test and it worked. It just amazes me as too obvious a problem for a product used by so many people, so I have to assume that I missed something.