log4net LogicalThreadContext 不工作

2024-01-07

我的问题要么是 log4net 中的错误,要么是我的误解。

我正在尝试使用LogicalThreadContext将某些数据与调用上下文相关联,并将其传播到该上下文中任何线程发出的任何日志语句。这就是所谓的优点LogicalThreadContext over 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 配置如下所示:

<?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 堆栈的数据仅出现在所做的日志记录语句中在同一个线程上。后台线程生成的日志语句缺少上下文数据。通过测试调试我可以看到,确实,LogicalThreadContext.Stacks.Count后台线程上为零。

深入研究 log4net 源代码,我发现它利用了CallContext http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.aspx班级。这个类的作用正如它所说的那样——它允许存储和检索当前“调用”的上下文。我不知道它是如何在低水平上做到这一点的。

CallContext有两组可以存储和检索上下文信息的方法:GetData/SetData and LogicalGetData/LogicalSetData。该文档很少介绍有关这两组方法之间差异的细节,但示例使用GetData/SetData。 log4net 也是如此LogicalThreadContext.

快速测试表明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 是否弄错了?或者我缺少什么?

UPDATE:我还尝试使用它的 log4net 进行自定义构建LogicalThreadContextProperties根据我上面的发现,班级发生了变化。我重新运行了最初的测试,结果成功了。对于这么多人使用的产品来说,这对我来说是一个太明显的问题,所以我不得不假设我错过了一些东西。


这是我之前问过的一个问题,关于 ThreadContext 和 LogicalThreadContext 之间的区别:

log4net.ThreadContext 和 log4net.LogicalThreadContext 有什么区别? https://stackoverflow.com/questions/3841075/what-is-the-difference-between-log4net-threadcontext-and-log4net-logicalthreadcon

其中有一个链接,指向 log4net 作者之一 Nicko Cadell 发布的有关 LogicalThreadContext 如何工作的帖子。他谈到存储在 CallContext 中的项目支持 ILogicalThreadAffinative 自动传播到子线程,但 log4net 不使用 ILogicalThreadAffinative。他没有提到任何有关使用 CallContext.LogicalSetData 的内容,正如您所发现的,这会导致 CallContext 数据自动传播到子线程,而无需实现 ILogicalThreadAffinative。

总之,我认为您没有遗漏任何东西。我确实认为 log4net 弄错了。

我意识到你没有要求任何代码,但这是我几个月前在研究 log4net、CallContext、PatternLayoutConverter 等时所做的一些工作。

首先,是我几个月前拼凑起来的“逻辑线程上下文”对象。我编写它是为了模仿 Castle 日志记录工具提供的日志记录上下文抽象。

  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)。它期望 Option 属性从逻辑调用上下文中指定特定的命名值。编写一个类似的 PatternLayoutConverter 并不会太难,它根据上面的名称从逻辑上下文中获取字典,然后使用 Option 参数对字典进行索引。

  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;
      }
    }
  }

要像第一个代码示例中那样使用字典方案,PatternLayoutConverter 可能看起来像这样(未编译和未经测试):

  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;
      }
    }
  }

祝你好运!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

log4net LogicalThreadContext 不工作 的相关文章

随机推荐