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