2011-08-04 53 views
8

我有什麼是log4net中的錯誤,或者是我的錯誤理解。log4net LogicalThreadContext不起作用

我試圖使用LogicalThreadContext將某些數據與調用上下文相關聯,並讓它傳播到該上下文中的任何線程所做的任何日誌語句。這是LogicalThreadContext優於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類。這個類完成它在錫上所說的 - 它允許存儲和檢索當前「調用」的上下文。它如何在低層次上做到這一點,我不知道。

CallContext具有兩組的方法與上下文信息可被存儲和檢索:GetData/SetDataLogicalGetData/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得到這個錯誤?還是有我失蹤的東西?

更新:我也想這樣做的log4net的自定義生成其改變按我上面的調查結果LogicalThreadContextProperties類。我重新運行了我的初始測試,它工作。這對我這麼多人使用的產品來說太顯而易見了,所以我不得不假設我錯過了一些東西。

回答

5

這裏是我問過一些時間回到大約有什麼區別ThreadContext和LogicalThreadContext之間的問題:

What is the difference between log4net.ThreadContext and log4net.LogicalThreadContext?

書中有一個帖子由力高Cadell,log4net的一個鏈接作者,關於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; 
     } 
    } 
    } 

祝你好運!

+1

值得注意的是,這是[固定](https://issues.apache.org/jira/browse/LOG4NET-317)在log4net 1.2.12中,但似乎還沒有發佈日期呢; https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=10690&version=12318546 –