UPDATE: 2014年12月11日 - 在這裏看到我的文章的第一部分:
What is the difference between log4net.ThreadContext and log4net.LogicalThreadContext?
的最新更新。 Log4Net的LogicalThreadContext在最近幾年(最近幾年)有所更新,現在它可以正常工作。鏈接文章中的更新提供了一些細節。
END UPDATE。
這是一個可能對你有幫助的想法。部分問題是log4net上下文對象(ThreadContext和LogicalThreadContext)不會將其屬性「流」到「子」線程。 LogicalThreadContext給出了它的錯誤印象,但它沒有。它在內部使用CallContext.SetData來存儲其屬性。通過SetData設置的數據附加到THREAD,但它不被子線程「繼承」。所以,如果你通過這樣的設置屬性:
log4net.LogicalThreadContext.Properties["myprop"] = "abc";
該屬性將通過百分比特性模式轉換爲loggable並從那裏您設置該屬性在首位在同一個線程登錄時將包含一個值,但它不會包含從該線程派生的任何子線程中的值。
如果您可以通過CallContext.LogicalSetData(請參閱上面的鏈接)保存您的屬性,那麼這些屬性會「流動」到任何子線程(或由其繼承)。所以,如果你可以做這樣的事情:
CallContext.LogicalSetData("MyLogicalData", nameStr + Thread.CurrentThread.ManagedThreadId);
然後「MyLogicalData」將可在您設置,以及在任何子線程的線程。
有關使用CallContext.LogicalSetData的更多信息,請參閱this blog posting by Jeffrey Richter。
您可以通過CallContext.LogicalSetData輕鬆存儲您自己的信息,並可通過編寫自己的PatternLayoutConverter通過log4net進行日誌記錄。我附加了兩個新的PatternLayoutConverters的示例代碼。
第一個允許您記錄存儲在Trace.CorrelationManager的LogicalOperationStack中的信息。佈局轉換器允許您記錄LogicalOperationStack的頂部或整個LogicalOperationStack。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using log4net.Util;
using log4net.Layout.Pattern;
using log4net.Core;
using System.Diagnostics;
namespace Log4NetTest
{
class LogicalOperationStackPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
string los = "";
if (String.IsNullOrWhiteSpace(Option) || String.Compare(Option.Substring(0, 1), "A", true) == 0)
{
//Log ALL of stack
los = Trace.CorrelationManager.LogicalOperationStack.Count > 0 ?
string.Join(">>",Trace.CorrelationManager.LogicalOperationStack.ToArray()) :
"";
}
else
if (String.Compare(Option.Substring(0, 1), "T", true) == 0)
{
//Log TOP of stack
los = Trace.CorrelationManager.LogicalOperationStack.Count > 0 ?
Trace.CorrelationManager.LogicalOperationStack.Peek().ToString() : "";
}
writer.Write(los);
}
}
}
第二個允許您記錄通過CallContext.LogicalSetData存儲的信息。正如所寫的,它使用CallContext.LogicalGetData使用固定名稱來拉取值。它可以很容易地修改爲使用Options屬性(如LogicalOperationStack轉換器中所演示的)來指定一個特定的值來使用CallContext.LogicalGetData進行拉取。
using log4net;
using log4net.Util;
using log4net.Layout.Pattern;
using log4net.Core;
using System.Runtime.Remoting.Messaging;
namespace Log4NetTest
{
class LogicalCallContextPatternConverter : PatternLayoutConverter
{
protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
{
string output = "";
object value = CallContext.LogicalGetData("MyLogicalData");
if (value == null)
{
output = "";
}
else
{
output = value.ToString();
}
writer.Write(output);
}
}
}
下面是如何配置:
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %logger %-5p [PROP = %property] [LOS.All = %LOS{a}] [LOS.Top = %LOS{t}] [LCC = %LCC] %m%n"/>
<converter>
<name value="LOS" />
<type value="Log4NetTest.LogicalOperationStackPatternConverter" />
</converter>
<converter>
<name value="LCC" />
<type value="Log4NetTest.LogicalCallContextPatternConverter" />
</converter>
</layout>
這裏是我的測試代碼:
//Start the threads
new Thread(TestThis).Start("ThreadA");
new Thread(TestThis).Start("ThreadB");
//Execute this code in the threads
private static void TestThis(object name)
{
var nameStr = (string)name;
Thread.CurrentThread.Name = nameStr;
log4net.ThreadContext.Properties["ThreadContext"] = nameStr;
log4net.LogicalThreadContext.Properties["LogicalThreadContext"] = nameStr;
CallContext.LogicalSetData("MyLogicalData", nameStr + Thread.CurrentThread.ManagedThreadId);
Trace.CorrelationManager.StartLogicalOperation(nameStr + Thread.CurrentThread.ManagedThreadId);
logger.Debug("From Thread itself");
ThreadPool.QueueUserWorkItem(x =>
{
logger.Debug("From threadpool Thread_1: " + nameStr);
Trace.CorrelationManager.StartLogicalOperation(nameStr + Thread.CurrentThread.ManagedThreadId);
CallContext.LogicalSetData("MyLogicalData", nameStr + Thread.CurrentThread.ManagedThreadId);
logger.Debug("From threadpool Thread_2: " + nameStr);
CallContext.FreeNamedDataSlot("MyLogicalData");
Trace.CorrelationManager.StopLogicalOperation();
logger.Debug("From threadpool Thread_3: " + nameStr);
});
}
這裏是輸出:
Form1: 2011-01-14 09:18:53,145 [ThreadA] Form1 DEBUG [PROP = {LogicalThreadContext=ThreadA, log4net:HostName=WILLIE620, ThreadContext=ThreadA}] [LOS.All = ThreadA10] [LOS.Top = ThreadA10] [LCC = ThreadA10] From Thread itself
Form1: 2011-01-14 09:18:53,160 [ThreadB] Form1 DEBUG [PROP = {LogicalThreadContext=ThreadB, log4net:HostName=WILLIE620, ThreadContext=ThreadB}] [LOS.All = ThreadB11] [LOS.Top = ThreadB11] [LCC = ThreadB11] From Thread itself
Form1: 2011-01-14 09:18:53,192 [12] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadB11] [LOS.Top = ThreadB11] [LCC = ThreadB11] From threadpool Thread_1: ThreadB
Form1: 2011-01-14 09:18:53,207 [12] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadB12>>ThreadB11] [LOS.Top = ThreadB12] [LCC = ThreadB12] From threadpool Thread_2: ThreadB
Form1: 2011-01-14 09:18:53,207 [12] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadB11] [LOS.Top = ThreadB11] [LCC = ] From threadpool Thread_3: ThreadB
Form1: 2011-01-14 09:18:53,207 [13] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadA10] [LOS.Top = ThreadA10] [LCC = ThreadA10] From threadpool Thread_1: ThreadA
Form1: 2011-01-14 09:18:53,223 [13] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadA13>>ThreadA10] [LOS.Top = ThreadA13] [LCC = ThreadA13] From threadpool Thread_2: ThreadA
Form1: 2011-01-14 09:18:53,223 [13] Form1 DEBUG [PROP = {log4net:HostName=WILLIE620}] [LOS.All = ThreadA10] [LOS.Top = ThreadA10] [LCC = ] From threadpool Thread_3: ThreadA
當我做這個測試(還有一些我一直在做的測試),我創建了我的自己的「上下文」堆棧對象(類似於log4net的「堆棧」實現)通過存儲我的堆棧通過CallContext.LogicalSetData而不是通過CallContext.SetData(這是如何log4net存儲它)。當我有多個ThreadPool線程時,我發現我的堆棧混亂了。也許是從退出子上下文時將數據合併回父上下文。我不會認爲會是這種情況,因爲在我的測試中,我顯式地將新值輸入到ThreadPool線程並在退出時彈出。基於Trace.CorrelationManager.LogicalOperationStack的實現(我寫了一個抽象概念)的類似測試似乎表現正確。我想也許自動流動(向下和向後)的邏輯是佔相關管理器,因爲它是系統中的「已知」對象?
有些事情要在輸出注:
Trace.CorrelationManager inforamation通過CallContext.LogicalSetData存儲,因此它是「流」到子線程。 TestThis使用Trace.CorrelationManager.StartLogicalOperation將邏輯操作(爲傳入的名稱命名)「推入」LogicalOperationStack。 ThreadPool線程中的第一個logger.Debug語句顯示ThreadPool線程繼承了與父線程相同的LogicalOperationStack。在ThreadPool線程內部,我開始一個新的邏輯操作,它被堆疊到繼承的LogicalOperationStack上。您可以在第二個記錄器中看到該結果。調試輸出。最後,在離開之前,我停止了邏輯運算。第三個記錄器。調試輸出顯示。
從輸出中可以看出,CallContext.LogicalSetData也是「流」到子線程。在我的測試代碼中,我選擇在ThreadPool線程中將新值設置爲LogicalSetData,然後在離開(FreeNamedDataSlot)之前將其清理乾淨。
隨意嘗試這些模式佈局轉換器,看看你是否能夠實現你正在尋找的結果。正如我已經證明的那樣,您至少應該能夠在日誌輸出中反映哪些ThreadPool線程是由其他(父級?)線程啓動/使用的。
注有在某些環境中的一些問題甚至CallContext.LogicalSetData:
「孩子」 的邏輯數據合併到 「父」 邏輯數據: EndInvoke changes current CallContext - why?
Nested multithread operations tracing
(不一個問題,但一個關於Trace.CorrelationManager.ActivityId和Task Parallel Library的好帖子):
How do Tasks in the Task Parallel Library affect ActivityID?
一個關於ASP上下文中各種「上下文」存儲機制問題的博客文章。淨
http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html
[編輯]
我發現,保持正確的上下文,而大量(或甚至不那麼嚴重 - 我的測試中使用不同的線程/任務/並行技術執行DoLongRunningWork)使用線程可以用CallContext.LogicalSetData拋出一些數據集。
請參閱this question about using Trace.CorrelationManager.ActivityId這裏StackOverflow。我發佈了一個關於使用Trace.CorrelationManager.LogicalOperationStack和我的一些觀察結果的答案。
後來我用我對這個問題的回答作爲基礎for my own question about using Trace.CorrelationManager.LogicalOperationStack in the context of Threads/Tasks/Parallel。
我也貼過a very similar question on Microsoft's Parallel Extensions forum。
你可以閱讀這些帖子看看我的觀察。簡單地總結一下:
有了這樣的代碼模式:
DoLongRunningWork //Kicked off as a Thread/Task/Parallel(.For or .Invoke)
StartLogicalOperation
Sleep(3000) //Or do actual work
StopLogicalOperation
的LogicalOperationStack的內容保持一致DoLongRunningWork是否被明確的主題/線程池線程/任務/並行(或。對於.Invoke拉開序幕)。
有了這樣的代碼圖案:
StartLogicalOperation //In Main thread (or parent thread)
DoLongRunningWork //Kicked off as a Thread/Task/Parallel(.For or .Invoke)
StartLogicalOperation
Sleep(3000) //Or do actual work
StopLogicalOperation
StopLogicalOperation
的LogicalOperationStack的內容保持時DoLongRunningWork通過的Parallel.For或Parallel.Invoke踢掉除非是一致的。原因似乎與Parallel.For和Parallel.Invoke使用主線程作爲執行並行操作的線程之一有關。
這意味着如果要將整個並行(或線程)操作作爲單個邏輯操作和每次迭代(即委託的每次調用)作爲嵌套在外部操作中的邏輯操作進行封裝,則大多數技術測試(線程/線程池/任務)正常工作。在每次迭代中,LogicalOperationStack都反映有一個外部任務(用於主線程)和一個內部任務(代表)。
如果使用Parallel.For或Parallel.Invoke,則LogicalOperationStack無法正常工作。在上面鏈接的帖子中的示例代碼中,LogicalOperationStack應該永遠不會超過2個條目。一個用於主線程,另一個用於代表。當使用Parallel.For或Parallel.Invoke時,LogicalOperationStack最終將獲得多於2個條目。
使用CallContext.LogicalSetData票價更糟糕(至少如果試圖通過存儲具有LogicalSetData的堆棧來模擬LogicalOperationStack)。使用與上面類似的調用模式(包含邏輯操作和委託邏輯操作的調用模式),與LogicalSetData一起存儲且保持相同(據我所知)的堆棧在幾乎所有情況下都會損壞。
CallContext.LogicalSetData可能適用於更簡單的類型或未在「邏輯線程」中修改的類型。如果我要用LogicalSetData存儲一個值的字典(類似於log4net.LogicalThreadContext.Properties),它可能會被子線程/ Tasks /等成功繼承。
對於這種情況發生的原因或最佳解決方法,我沒有任何很好的解釋。這可能是因爲我測試「環境」的方式有點過分,或者可能不會。
如果你仔細研究這一點,你可以試試我在上面鏈接中發佈的測試程序。測試程序只測試LogicalOperationStack。我通過創建一個支持像IContextStack這樣的接口的上下文抽象,對更復雜的代碼進行了類似的測試。一個實現使用通過CallContext.LogicalSetData存儲的堆棧(類似於存儲log4net的LogicalThreadContext.Stacks,除了我使用LogicalSetData而不是SetData)。另一個實現通過Trace.CorrelationManager.LogicalOperationStack實現該接口。這使我可以輕鬆地使用不同的上下文實現來運行相同的測試。
這裏是我的IContextStack接口:
public interface IContextStack
{
IDisposable Push(object item);
object Pop();
object Peek();
void Clear();
int Count { get; }
IEnumerable<object> Items { get; }
}
這是基於LogicalOperationStack-implementaiton:
class CorrelationManagerStack : IContextStack, IEnumerable<object>
{
#region IContextStack Members
public IDisposable Push(object item)
{
Trace.CorrelationManager.StartLogicalOperation(item);
return new StackPopper(Count - 1, this);
}
public object Pop()
{
object operation = null;
if (Count > 0)
{
operation = Peek();
Trace.CorrelationManager.StopLogicalOperation();
}
return operation;
}
public object Peek()
{
object operation = null;
if (Count > 0)
{
operation = Trace.CorrelationManager.LogicalOperationStack.Peek();
}
return operation;
}
public void Clear()
{
Trace.CorrelationManager.LogicalOperationStack.Clear();
}
public int Count
{
get { return Trace.CorrelationManager.LogicalOperationStack.Count; }
}
public IEnumerable<object> Items
{
get { return Trace.CorrelationManager.LogicalOperationStack.ToArray(); }
}
#endregion
#region IEnumerable<object> Members
public IEnumerator<object> GetEnumerator()
{
return (IEnumerator<object>)(Trace.CorrelationManager.LogicalOperationStack.ToArray().GetEnumerator());
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return Trace.CorrelationManager.LogicalOperationStack.ToArray().GetEnumerator();
}
#endregion
}
這是基於CallContext.LogicalSetData的實現:
class ThreadStack : IContextStack, IEnumerable<object>
{
const string slot = "EGFContext.ThreadContextStack";
private static Stack<object> GetThreadStack
{
get
{
Stack<object> stack = CallContext.LogicalGetData(slot) as Stack<object>;
if (stack == null)
{
stack = new Stack<object>();
CallContext.LogicalSetData(slot, stack);
}
return stack;
}
}
#region IContextStack Members
public IDisposable Push(object item)
{
Stack<object> s = GetThreadStack;
int prevCount = s.Count;
GetThreadStack.Push(item);
return new StackPopper(prevCount, this);
}
public object Pop()
{
object top = GetThreadStack.Pop();
if (GetThreadStack.Count == 0)
{
CallContext.FreeNamedDataSlot(slot);
}
return top;
}
public object Peek()
{
return Count > 0 ? GetThreadStack.Peek() : null;
}
public void Clear()
{
GetThreadStack.Clear();
CallContext.FreeNamedDataSlot(slot);
}
public int Count { get { return GetThreadStack.Count; } }
public IEnumerable<object> Items
{
get
{
return GetThreadStack;
}
}
#endregion
#region IEnumerable<object> Members
public IEnumerator<object> GetEnumerator()
{
return GetThreadStack.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetThreadStack.GetEnumerator();
}
#endregion
}
這裏是由兩者使用的StackPopper:
internal class StackPopper : IDisposable
{
int pc;
IContextStack st;
public StackPopper(int prevCount, IContextStack stack)
{
pc = prevCount;
st = stack;
}
#region IDisposable Members
public void Dispose()
{
while (st.Count > pc)
{
st.Pop();
}
}
#endregion
}
這是很多消化,但也許你會發現一些有用的!
@我的其他我 - (添加此評論希望你會得到通知,無論她在我的更長的評論下面)請參閱我的評論/問題迴應你的2010年11月4日評論回答@TskTsk – wageoghe 2011-01-05 20:14:14