log4net LogicalThreadContext nicht funktioniert
Ich haben, was entweder ein Fehler in der log4net, oder ein Missverständnis meinerseits.
Ich versuche zu verwenden LogicalThreadContext
zu assoziieren einige Daten mit einem Aufruf von Kontext und haben es weitergegeben, um alle log-Anweisungen, die von einem thread in diesem Zusammenhang. Das ist der angebliche Vorteil von LogicalThreadContext
über ThreadContext
.
War ich nicht in der Lage, um die Ausbreitung zu arbeiten, so habe ich zusammen ein einfaches unit-test, um zu sehen, ob es funktionieren würde, und es nicht. Hier ist es:
[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");
}
}
Meine log4net Konfiguration sieht so aus:
<?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>
Und meine Ausgabe sieht wie folgt aus:
[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
Wie Sie sehen können, werden die Daten Schiebe ich auf die LTC-stack ist nur die Protokollierung von Aussagen auf dem gleichen thread. Die log-Anweisung aus, indem Sie die hintergrund-thread fehlt der Kontext-Daten. Debugging durch den test konnte ich sehen, dass, ja, dass LogicalThreadContext.Stacks.Count
ist null auf der hintergrund-thread.
Graben in der log4net-Quelle, ich fand es unter Verwendung der CallContext
Klasse. Diese Klasse tut, was es auf dem zinn sagt - es erlaubt den Kontext für das aktuelle "call", um die gespeichert und abgerufen werden. Wie es das macht, auf einem niedrigen Niveau, ich habe keine Ahnung.
CallContext
hat zwei Sätze von Methoden, mit denen die Kontext-Informationen, die gespeichert und kann abgerufen werden: GetData
/SetData
und LogicalGetData
/LogicalSetData
. Die Dokumentation ist sehr Licht auf details in Bezug auf den Unterschied zwischen diesen zwei Gruppen von Methoden, aber die Beispiele verwenden GetData
/SetData
. Und so funktioniert log4net ist LogicalThreadContext
.
Ein kurzer test zeigte, dass GetData
/SetData
zeigt das gleiche problem - Daten nicht verteilt threads. Ich dachte, ich würde geben LogicalGetData
/LogicalSetData
ein go statt:
[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();
}
}
Dieser test geht - Kontext-information erfolgreich weitergegeben threads bei der Verwendung von LogicalGetData
/LogicalSetData
.
Also meine Frage ist: hat log4net bekommen dies falsch? Oder ist es etwas, was ich bin fehlt?
UPDATE: ich auch versucht zu tun eine benutzerdefinierte build von log4net mit seinen LogicalThreadContextProperties
Klasse geändert, wie pro meine Erkenntnisse vor. Ich wieder ran mein Erster test und es funktionierte. Dies eben scheint mir zu offensichtlich ein problem für ein Produkt von so vielen Menschen, muss ich also davon ausgehen, ich bin etwas fehlt.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Hier ist eine Frage, die ich fragte einige Zeit zurück, was der Unterschied ist zwischen ThreadContext und LogicalThreadContext:
Was ist der Unterschied zwischen log4net.ThreadContext und log4net.LogicalThreadContext?
Dort ist ein link zu einem posting von Nicko Cadell, eine der log4net-Autoren, etwa wie die LogicalThreadContext funktioniert. Er spricht über die gespeicherten Elemente in den CallContext, dass die Unterstützung ILogicalThreadAffinative automatisch an untergeordnete threads aber, dass log4net verwenden, die nicht mit ILogicalThreadAffinative. Er erwähnt nicht, etwas über die Verwendung der CallContext.LogicalSetData, die, wie Sie herausgefunden haben, bewirkt, dass CallContext Daten weitergegeben werden an untergeordnete threads automatisch ohne Implementierung ILogicalThreadAffinative.
Abschließend, ich glaube nicht, dass Ihnen etwas fehlt. Ich denke, dass log4net geworden ist es falsch.
Ich erkennen, dass Sie nicht Fragen, für jeden code, aber hier ist einige Arbeit, ich habe vor einigen Monaten, als ich war auf der Suche in log4net, CallContext, PatternLayoutConverter, etc.
Zunächst eine "logische thread-context" - Objekt, das warf ich zusammen vor einigen Monaten. Ich schrieb es zu imitieren, die Protokollierung Kontext Abstraktionen zur Verfügung gestellt von der Burg-logging-Funktion.
Hier ist eine log4net PatternLayoutConverter (geschrieben wurde, zu einer anderen Zeit, als in Erster Linie ein experiment, um zu helfen, erfahren Sie mehr über log4net und die CallContext). Es erwartet, dass die Option-Eigenschaft zum angeben eines bestimmten namens mit dem Wert aus dem logischen Aufruf-Kontext. Es würde nicht allzu schwer sein, schreiben einen ähnlichen PatternLayoutConverter hat, dass das Wörterbuch, aus dem logischen Kontext, basierend auf den Namen oben, und dann auf den Option-parameter index ins Wörterbuch.
Verwenden das dictionary-Schema wie im ersten code-Beispiel, das PatternLayoutConverter könnte wie folgt Aussehen (unkompilierte und ungetestet):
Glück!