title: How Thread-Safe is NLog?
body: <p>Well, </p>
  
  <p>I have waited for days before deciding to post this issue, as I was not sure how to state this, resutling into a long detailed post. However, I think it is relevant to ask for the community's help at this point.</p>
  
  <p>Basically, I tried to use NLog to configure loggers for hundreds of threads. I thought this would be very straightforward, But I got this exception after few tens of seconds : 
  "<em>InvalidOperationException : Collection was modified; enumeration operation may not execute</em>"</p>
  
  <p>Here is the code.</p>
  
  <pre><code>//Launches threads that initiate loggers
  class ThreadManager
  {
      //(...)
      for (int i = 0; i&lt;500; i++)
      {
          myWorker wk = new myWorker();
          wk.RunWorkerAsync();
      }
  
      internal class myWorker : : BackgroundWorker
      {             
         protected override void OnDoWork(DoWorkEventArgs e)
         {              
             // "Logging" is Not static - Just to eliminate this possibility 
             // as an error culprit
             Logging L = new Logging(); 
             //myRandomID is a random 12 characters sequence
             //iLog Method is detailed below
            Logger log = L.iLog(myRandomID);
            base.OnDoWork(e);
         }
      }
  }
  
  public class Logging
  {   
          //ALL THis METHOD IS VERY BASIC NLOG SETTING - JUST FOR THE RECORD
          public Logger iLog(string loggerID)
          {
          LoggingConfiguration config;
          Logger logger;
          FileTarget FileTarget;            
          LoggingRule Rule; 
  
          FileTarget = new FileTarget();
          FileTarget.DeleteOldFileOnStartup = false;
          FileTarget.FileName =  "X:\\" + loggerID + ".log";
  
          AsyncTargetWrapper asyncWrapper = new AsyncTargetWrapper();
          asyncWrapper.QueueLimit = 5000;
          asyncWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard;
          asyncWrapper.WrappedTarget = FileTarget;
  
          //config = new LoggingConfiguration(); //Tried to Fool NLog by this trick - bad idea as the LogManager need to keep track of all config content (which seems to cause my problem;               
          config = LogManager.Configuration;                
          config.AddTarget("File", asyncWrapper);                
          Rule = new LoggingRule(loggerID, LogLevel.Info, FileTarget);
  
          lock (LogManager.Configuration.LoggingRules)
              config.LoggingRules.Add(Rule);                
  
          LogManager.Configuration = config;
          logger = LogManager.GetLogger(loggerID);
  
          return logger;
      }
  }   
  </code></pre>
  
  <p>So I did my job an rather than just posting my question here and having a family-quality-time, I spent the week-end digging on that (Lucky Boy ! ) 
  I downloaded the latest stable release of NLOG 2.0 and included it in my project. I was able to trace the exact place where it blowed  : </p>
  
  <p>in LogFactory.cs : </p>
  
  <pre><code>    internal void GetTargetsByLevelForLogger(string name, IList&lt;LoggingRule&gt; rules, TargetWithFilterChain[] targetsByLevel, TargetWithFilterChain[] lastTargetsByLevel)
      {
          //lock (rules)//&lt;--Adding this does not fix it
              foreach (LoggingRule rule in rules)//&lt;-- BLOWS HERE
              {
              }
       }
  </code></pre>
  
  <p>in LoggingConfiguration.cs : </p>
  
  <pre><code>internal void FlushAllTargets(AsyncContinuation asyncContinuation)
      {            
          var uniqueTargets = new List&lt;Target&gt;();
          //lock (LoggingRules)//&lt;--Adding this does not fix it
          foreach (var rule in this.LoggingRules)//&lt;-- BLOWS HERE
          {
          }
       }
  </code></pre>
  
  <p><strong>The problem according to me</strong> <br>
  So, based on my understanding, what happens is that the LogManager gets mixed up as there are <strong>calls to config.LoggingRules.Add(Rule) from different threads</strong> while <em>GetTargetsByLevelForLogger</em> and <em>FlushAllTargets</em> are being called. 
  I tried to screw the foreach and replace it by a for loop but the logger turned rogue (skipping many log files creation)</p>
  
  <p>SOoooo <strong>FINALLY</strong> <br>
  It is written everywhere that NLOG is thread-safe, but I've come through a few posts that dig things further and claim that this depends on the utilization scenario. What about my case ?
  I have to create thousands of loggers (not all at the same time, but still at a very high pace).</p>
  
  <p>The workaround I have found is to create all loggers in the SAME MASTER THREAD; this is <em>REALLY</em> unconvenient, as I create all my app loggers at the start of the app (sort of a Logger Pool). 
  And though it works sweet, it is just NOT an acceptable design.</p>
  
  <p>So you know it all Folks. 
  Please help a coder see his family again.</p>