【发布时间】:2011-08-04 09:29:08
【问题描述】:
我的问题是 log4net 中的错误,或者是我的误解。
我正在尝试使用LogicalThreadContext 将一些数据与调用上下文相关联,并将其传播到该上下文中任何线程所做的任何日志语句。这就是LogicalThreadContext 相对于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 类。这个类做它在锡上所说的 - 它允许存储和检索当前“调用”的上下文。它是如何在低级别做到这一点的,我不知道。
CallContext 有两组可以存储和检索上下文信息的方法:GetData/SetData 和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 搞错了吗?还是我遗漏了什么?
更新:我还尝试根据我上面的发现对其 LogicalThreadContextProperties 类进行自定义构建 log4net。我重新运行了我的初始测试,它成功了。对于这么多人使用的产品来说,这让我觉得问题太明显了,所以我不得不假设我遗漏了一些东西。
【问题讨论】:
标签: .net multithreading log4net