Well,
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.
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 : "InvalidOperationException : Collection was modified; enumeration operation may not execute"
Here is the code.
//Launches threads that initiate loggers
class ThreadManager
{
//(...)
for (int i = 0; i<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;
}
}
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 :
in LogFactory.cs :
internal void GetTargetsByLevelForLogger(string name, IList<LoggingRule> rules, TargetWithFilterChain[] targetsByLevel, TargetWithFilterChain[] lastTargetsByLevel)
{
//lock (rules)//<--Adding this does not fix it
foreach (LoggingRule rule in rules)//<-- BLOWS HERE
{
}
}
in LoggingConfiguration.cs :
internal void FlushAllTargets(AsyncContinuation asyncContinuation)
{
var uniqueTargets = new List<Target>();
//lock (LoggingRules)//<--Adding this does not fix it
foreach (var rule in this.LoggingRules)//<-- BLOWS HERE
{
}
}
The problem according to me
So, based on my understanding, what happens is that the LogManager gets mixed up as there are calls to config.LoggingRules.Add(Rule) from different threads while GetTargetsByLevelForLogger and FlushAllTargets 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)
SOoooo FINALLY
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).
The workaround I have found is to create all loggers in the SAME MASTER THREAD; this is REALLY 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.
So you know it all Folks. Please help a coder see his family again.