2011-08-04 22 views
8

मेरे पास लॉग 4नेट में एक बग है, या मेरे हिस्से पर गलतफहमी है।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 

आप देख सकते हैं, डेटा मैं पुश एलटीसी स्टैक केवल को उसी थ्रेड पर लॉगिंग स्टेटमेंट में मौजूद है। पृष्ठभूमि धागे द्वारा किए गए लॉग स्टेटमेंट में प्रासंगिक डेटा की कमी है। परीक्षण के माध्यम से डीबगिंग मैं देख सकता था कि, वास्तव में, LogicalThreadContext.Stacks.Count पृष्ठभूमि धागे पर शून्य है।

लॉग 4नेट स्रोत में खोदने के बाद, मैंने इसे CallContext कक्षा का उपयोग किया। यह वर्ग टिन पर जो कहता है वह करता है - यह वर्तमान "कॉल" को संदर्भित करने और पुनर्प्राप्त करने के लिए संदर्भ की अनुमति देता है। यह निम्न स्तर पर कैसे करता है, मुझे कोई जानकारी नहीं है। GetData/SetData और LogicalGetData/LogicalSetData:

CallContext तरीकों के दो सेट है जिसके साथ संदर्भ जानकारी संग्रहीत और पुनः प्राप्त किया जा सकता है। दस्तावेजों के इन दो सेटों के बीच अंतर के बारे में विवरणों पर दस्तावेज बहुत हल्का है, लेकिन उदाहरण 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 का उपयोग कर धागे भर में प्रचारित किया जाता है।

तो मेरा सवाल यह है: क्या लॉग 4net ने यह गलत पाया है? या क्या मुझे कुछ याद आ रहा है?

अद्यतन: मैं भी अपने LogicalThreadContextProperties श्रेणी से ऊपर मेरे निष्कर्ष के अनुसार बदल के साथ log4net के एक कस्टम निर्माण कर रही है की कोशिश की। मैंने अपना प्रारंभिक परीक्षण फिर से चलाया और यह काम किया। यह मुझे इतने सारे लोगों द्वारा उपयोग किए जाने वाले उत्पाद के लिए बहुत स्पष्ट समस्या के रूप में मारता है, इसलिए मुझे लगता है कि मुझे कुछ याद आ रहा है।

उत्तर

5

यहाँ एक सवाल है कि मैं कुछ समय के लिए कहा वापस के बारे में क्या अंतर ThreadContext और LogicalThreadContext के बीच है:

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

निको Cadell, log4net में से एक ने एक पोस्ट करने के लिए वहाँ में एक कड़ी है लेखकों के बारे में, LogicalThreadContext कैसे काम करता है। वह CallContext में संग्रहीत वस्तुओं के बारे में बात करता है जो ILogicalThreadAffinative को स्वचालित रूप से बाल धागे के लिए प्रचारित करने का समर्थन करता है लेकिन वह log4net ILogicalThreadAffinative का उपयोग नहीं करता है। वह CallContext.LogicalSetData का उपयोग करने के बारे में कुछ भी नहीं बताता है, जैसा कि आपने पाया है, कॉलकॉन्टेक्स्ट डेटा को ILogicalThreadAffinative को लागू किए बिना स्वचालित रूप से बाल धागे को प्रचारित करने का कारण बनता है।

निष्कर्ष में, मुझे नहीं लगता कि आप कुछ भी खो रहे हैं। मुझे लगता है कि log4net इसे गलत मिला है।

मुझे लगता है कि आप किसी भी कोड के लिए नहीं किया था, लेकिन यहां कुछ काम है कि मैं कुछ महीने पहले, CallContext, PatternLayoutConverter, आदि

, सबसे पहले एक "तार्किक धागा संदर्भ किया था जब मैं log4net में देख रहा था है "ऑब्जेक्ट जो मैंने कई महीने पहले एक साथ फेंक दिया था। मैंने इसे कैसल लॉगिंग सुविधा द्वारा प्रदान किए गए लॉगिंग संदर्भ अबाउट्रिक की नकल करने के लिए लिखा था।

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 के बारे में जानने में मदद करने के लिए) है। यह विकल्प संपत्ति को तार्किक कॉल संदर्भ से एक विशेष नामित मान निर्दिष्ट करने की अपेक्षा करता है। एक समान पैटर्न LayoutConverter लिखना बहुत कठिन नहीं होगा जिसने उपरोक्त नाम के आधार पर तार्किक संदर्भ से शब्दकोश प्राप्त किया है, और फिर शब्दकोश में अनुक्रमणिका के लिए विकल्प पैरामीटर का उपयोग किया।

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 इस (uncompiled और 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; 
     } 
    } 
    } 

गुड लक!

+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 –

संबंधित मुद्दे