log4net的LogicalThreadContext不工作 [英] log4net LogicalThreadContext not working

查看:266
本文介绍了log4net的LogicalThreadContext不工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有什么是无论是在log4net的错误,或者对我而言是一种误解。

I have what is either a bug in log4net, or a misunderstanding on my part.

我试图使用 LogicalThreadContext 来的一些数据与调用上下文关联起来,并把它传播到在这方面的任何线程所做的任何日志语句。这就是 LogicalThreadContext 声称的优势 ThreadContext

I'm trying to use LogicalThreadContext to associate some data with a call context and have it propagate to any log statements made by any thread in that context. That is the purported advantage of LogicalThreadContext over ThreadContext.

我是不是能够得到传播工作,所以我把一个简单的单元测试,看看它是否会工作,它没有。在这里,它是:

I wasn't able to get the propagation to work, so I put together a simple unit test to see whether it would work, and it doesn't. Here it is:

[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的配置是这样的:

My log4net configuration looks like this:

<?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栈中的数据仅仅是present中所作的日志记录语句的在同一个线程的。由后台线程所做的日志语句缺乏上下文数据。通过试验调试,我可以看到的是,事实上,这 LogicalThreadContext.Stacks.Count 是在后台线程为零。

As you can see, the data I push onto the LTC stack is only present in the logging statements made on the same thread. The log statement made by the background thread is lacking the contextual data. Debugging through the test I could see that, indeed, that LogicalThreadContext.Stacks.Count is zero on the background thread.

挖掘到log4net的来源,我发现它利用<一href="http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx"><$c$c>CallContext类。该类做什么它在锡说 - 它允许范围内为目前呼地存储和检索。它是如何做到这一点,在一个较低的水平,我不知道。

Digging into the log4net source, I found it utilizing the CallContext class. This class does what it says on the tin - it allows context for the current "call" to be stored and retrieved. How it does this at a low level, I have no idea.

CallContext中有两套方法与上下文信息可以存储和检索:的GetData / 的SetData LogicalGetData / LogicalSetData 。该文件是关于这两套方法之间的差异的细节很轻,但示例使用的GetData / 的SetData 。所以做的log4net的 LogicalThreadContext

CallContext has two sets of methods with which context information can be stored and retrieved: GetData/SetData and LogicalGetData/LogicalSetData. The documentation is very light on details regarding the difference between these two sets of methods, but the examples use GetData/SetData. And so does log4net's LogicalThreadContext.

一个快速测试显示,的GetData / 的SetData 表现出了同样的问题 - 数据不跨线程传播。我想我给 LogicalGetData / LogicalSetData A去代替:

A quick test showed that GetData/SetData exhibits the same problem - data does not propagate across threads. I thought I'd give LogicalGetData/LogicalSetData a go instead:

[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

This test passes - contextual information is successfully propagated across threads when using LogicalGetData/LogicalSetData.

所以我的问题是这样的:已经log4net的得到了这个错误?或者是有什么我失踪?

So my question is this: has log4net gotten this wrong? Or is there something I'm missing?

更新:我也试图做的log4net其 LogicalThreadContextProperties 类改变按我上面的调查结果的自定义生成。我重新跑了我最初的测试和它的工作。这正好给我的印象太明显了一个用这么多的人一个产品的问题,所以我必须承担,我失去了一些东西。

UPDATE: I also tried doing a custom build of log4net with its LogicalThreadContextProperties class altered as per my findings above. I re-ran my initial test and it worked. This just strikes me as too obvious a problem for a product used by so many people, so I have to assume I'm missing something.

推荐答案

下面是一个问题,我问了一些时间回有关的区别是ThreadContext和LogicalThreadContext之间是什么:

Here is a question that I asked some time back about what the difference is between ThreadContext and LogicalThreadContext:

<一个href="http://stackoverflow.com/questions/3841075/what-is-the-difference-between-log4net-threadcontext-and-log4net-logicalthreadcon">What是log4net.ThreadContext和log4net.LogicalThreadContext之间的区别是什么?

有一个在那里的一个帖子被Nicko Cadell,log4net的作者之一,对LogicalThreadContext如何工作的一个环节。他谈到存储在支持ILogicalThreadAffinative被自动传播到子线程的CallContext中的项目,但该log4net的不使用ILogicalThreadAffinative。他没有提到任何关于使用CallContext.LogicalSetData,正如你已经发现,导致CallContext中的数据自动而无需实现ILogicalThreadAffinative传播到子线程。

There is a link in there to a posting by Nicko Cadell, one of the log4net authors, about how the LogicalThreadContext works. He talks about items stored in the CallContext that support ILogicalThreadAffinative being automatically propagated to child threads but that log4net does not use ILogicalThreadAffinative. He does not mention anything about using CallContext.LogicalSetData, which, as you have found, causes CallContext data to be propagated to child threads automatically without having to implement ILogicalThreadAffinative.

在最后,我不认为你缺少什么。我认为log4net的已经得到它错了。

In conclusion, I don't think that you are missing anything. I do think that log4net has gotten it wrong.

我知道你没有要求任何code,但这里是我做了一些几个月前的一些工作时,我一直在寻找到log4net的,CallContext中,PatternLayoutConverter等。

I realize that you did not ask for any code, but here is some work that I did some months ago when I was looking into log4net, CallContext, PatternLayoutConverter, etc.

首先,我扔在一起几个月前,一个逻辑线程上下文对象。我写的模仿的城堡记录工具提供的日志记录上下文的抽象。

First, a "logical thread context" object that I threw together several months ago. I wrote it to mimic the logging context abstractions provided by the Castle logging facility.

  public static class LogicalThreadDiagnosticContext
  {
    const string slot = "Logging.Context.LogicalThreadDiagnosticContext";

    internal static IDictionary<string, object> LogicalThreadDictionary
    {
      get
      {
        IDictionary<string, object> dict = (IDictionary<string, object>)CallContext.LogicalGetData(slot);
        if (dict == null)
        {
          dict = new Dictionary<string, object>();
          CallContext.LogicalSetData(slot, dict);
        }

        return dict;
      }
    }

    public new static string ToString()
    {
      if (LogicalThreadDictionary.Count == 0) return "";

      IEnumerable<string> es = (from kvp in LogicalThreadDictionary select string.Format("{0} = {1}", kvp.Key, kvp.Value));

      string s = string.Join(";", es);

      return s;
    }

    public static IDictionary<string, object> CloneProperties()
    {
      return new Dictionary<string, object>(LogicalThreadDictionary);
    }

    public static void Set(string item, object value)
    {
      LogicalThreadDictionary[item] = value;
    }

    public static object Get(string item)
    {
      object s;

      if (!LogicalThreadDictionary.TryGetValue(item, out s))
      {
        s = string.Empty;
      }

      return s;
    }

    public static bool Contains(string item)
    {
      return LogicalThreadDictionary.ContainsKey(item);
    }

    public static void Remove(string item)
    {
      LogicalThreadDictionary.Remove(item);
    }

    public static void Clear()
    {
      LogicalThreadDictionary.Clear();
    }

    public static int Count
    {
      get { return LogicalThreadDictionary.Count; }
    }
  }

下面是一个log4net的PatternLayoutConverter(这是写在不同的时间,主要是作为一个实验,以帮助了解log4net的和CallContext中)。该公司预计将选项属性指定从逻辑调用上下文特定的命名值。它不会太难写了从逻辑上下文字典根据上面的名称,然后使用选项参数索引到字典中类似的PatternLayoutConverter。

Here is a log4net PatternLayoutConverter (that was written at a different time, primarily as an experiment to help learn about log4net and the CallContext). It expects the Option property to specify a particular named value from the logical call context. It would not be too hard to write a similar PatternLayoutConverter that got the dictionary from the logical context based on the name above, and then used the Option parameter to index into the dictionary.

  class LogicalCallContextLayoutConverter : PatternLayoutConverter
  {
    private bool isDisabled = false;

    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      if (isDisabled || Option == null || Option.Length == 0) return;

      try
      {
        object data = CallContext.LogicalGetData(Option);
        if (data != null)
        {
          writer.Write(data.ToString());
        }
      }
      catch (SecurityException)
      {
        isDisabled = true;
      }
    }
  }

要使用的字典方案作为第一个code样品中,该PatternLayoutConverter可能会是这个样子(未编译和未经测试):

To use the dictionary scheme as in the first code sample, the PatternLayoutConverter might look something like this (uncompiled and untested):

  class LogicalCallContextLayoutConverter : PatternLayoutConverter
  {
    private bool isDisabled = false;

    protected override void Convert(System.IO.TextWriter writer, LoggingEvent loggingEvent)
    {
      if (isDisabled || Option == null || Option.Length == 0) return;

      try
      {
        object data = LogicalThreadDiagnosticContext[Option];
        if (data != null)
        {
          if (data != null)
          {
            writer.Write(data.ToString());
          }
        }
      }
      catch (SecurityException)
      {
        isDisabled = true;
      }
    }
  }

祝你好运!

这篇关于log4net的LogicalThreadContext不工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆