我的问题要么是 log4net 中的错误,要么是我的误解。
我正在尝试使用LogicalThreadContext
将某些数据与调用上下文相关联,并将其传播到该上下文中任何线程发出的任何日志语句。这就是所谓的优点LogicalThreadContext
over ThreadContext
.
我无法让传播发挥作用,因此我编写了一个简单的单元测试来查看它是否有效,但事实并非如此。这里是:
[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");
}
}
我的 log4net 配置如下所示:
<?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>
我的输出如下所示:
[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
如您所见,我推送到 LTC 堆栈的数据仅出现在所做的日志记录语句中在同一个线程上。后台线程生成的日志语句缺少上下文数据。通过测试调试我可以看到,确实,LogicalThreadContext.Stacks.Count
后台线程上为零。
深入研究 log4net 源代码,我发现它利用了CallContext http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx班级。这个类的作用正如它所说的那样——它允许存储和检索当前“调用”的上下文。我不知道它是如何在低水平上做到这一点的。
CallContext
有两组可以存储和检索上下文信息的方法:GetData
/SetData
and LogicalGetData
/LogicalSetData
。该文档很少介绍有关这两组方法之间差异的细节,但示例使用GetData
/SetData
。 log4net 也是如此LogicalThreadContext
.
快速测试表明GetData
/SetData
表现出同样的问题 - 数据不会跨线程传播。我以为我会给LogicalGetData
/LogicalSetData
改为:
[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();
}
}
此测试通过 - 使用时上下文信息已成功跨线程传播LogicalGetData
/LogicalSetData
.
所以我的问题是这样的:log4net 是否弄错了?或者我缺少什么?
UPDATE:我还尝试使用它的 log4net 进行自定义构建LogicalThreadContextProperties
根据我上面的发现,班级发生了变化。我重新运行了最初的测试,结果成功了。对于这么多人使用的产品来说,这对我来说是一个太明显的问题,所以我不得不假设我错过了一些东西。