У меня есть то, что является либо ошибкой в 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 выглядит так:
И мои выходные данные выглядят так:
[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
. Как и LogicalThreadContext
в log4net.
Быстрый тест показал, что 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? Или мне чего-то не хватает?
ОБНОВЛЕНИЕ: Я также пробовал создать собственную сборку log4net с его классом LogicalThreadContextProperties
, измененным согласно моим выводам выше. Я повторно провел свой первоначальный тест, и он сработал. Это просто кажется мне слишком очевидной проблемой для продукта, которым пользуется так много людей, поэтому я вынужден предположить, что я чего-то упускаю.