2

I have a function: processData(string taskName) and I want to generate multiple log files that contain the taskName parameter in their path using NLog.

for example:

processData("task1") should be logged only to C:\log_task1.txt

processData("task2") should be logged only to C:\log_task2.txt

processData("task3") should be logged only to C:\log_task3.txt

and so on.

NOTE: I don't know the values of the parameters in advance. This was just an example.

Robot Mess
  • 949
  • 1
  • 7
  • 31
  • Why does it have to be NLog? This doesn't appear to be a simple app logging functionality, but rather a business requirement. – vgru Aug 25 '14 at 14:17
  • @Groo: you are right it doesn't have to be nlog, but I use nlog for normal logging and since the tasks are run in a multithreaded environment, I don't really want to build my own logging framework. I will try the answers below before rolling my own. – Robot Mess Aug 26 '14 at 06:34

2 Answers2

2

In your NLog config, create different rules for each log file:

<targets>
    <target name="logfile1"
        xsi:type="File"
        fileName="C:\log_task1.log" />
    <target name="logfile2"
        xsi:type="File"
        fileName="C:\log_task2.log" />
    <target name="logfile3"
        xsi:type="File"
        fileName="C:\log_task3.log" />
</targets>
<rules>
    <logger name="task1"
        minlevel="Trace"
        writeTo="logfile1" />
    <logger name="task2"
        minlevel="Trace"
        writeTo="logfile2" />
    <logger name="task3"
        minlevel="Trace"
        writeTo="logfile3" />
</rules>

Then, in your method, use the corresponding NLog logger:

public void ProcessData(string param)
{
    var logger = NLog.LogManager.GetLogger(param);
}

If you don't know the value of the parameter in advance, you can create the NLog configuration programmatically at runtime (but don't forget to only allow whitelisted parameter values to prevent attacks that overwrite existing files at a location chosen by the attacker). How to programmatically configure NLog is described here:

NLog Configuration API

The code would be along the lines of this (just written down without test):

public void AddLogTarget(string param)
{
    if (NLog.LogManager.Configuration.FindTargetByName(param) == null)
    {
        var target = new FileTarget
        {
            Name = param,
            FileName = @"C:\" + param + ".log"
        };
        NLog.LogManager.Configuration.AddTarget(param, target);
        NLog.LogManager.Configuration.LoggingRules.Add(new LoggingRule(param, LogLevel.Debug, target));
    }
}
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • The problem is that I don't know the values of the parameter in advance. They will be different on different computers. – Robot Mess Aug 25 '14 at 09:32
0

There is an option that might work, but it will take some effort on your part to use it.

NLog has the ability to name the log file via most LayoutRenderers. In your case, you could configure your File target something like this:

<target name="FileTarget" xsi:type="File" fileName="${gdc:item=Task}.log" layout=${longdate} | ${logger} | ${level} | ${message}" />

Then, when you are executing a task, you can set the GlobalDiagnosticContext based on your task name:

void processData(String taskName)
{
   GlobalDiagnosticContext.Set("Task", taskName);
   log.Info("Hello from processData.  This should be in {0}.log", taskName);
   GlobalDiagnosticContext.Remove("Task");
}

This will cause all log messages that are written to be written to the desired log file.

This might work fine if your application is single threaded, so you don't have to worry about collisions between simultaneous executions of processData.

If you are using threads, you might get good results using the ThreadDiagnosticContext in the same way.

Another idea is to manually create LogEventInfo objects and use the Log method to log them yourself. Something like this:

var LogEventInfo le = new LogEventInfo(LogLevel.Info, logger.Name, "Hello!");
le.Properties["Task"] = taskName;
logger.Log(le);

In this case you would use the EventContext LayoutRenderer to build the log filename:

<target name="FileTarget" xsi:type="File" fileName="${event-context:item=Task}.log" layout=${longdate} | ${logger} | ${level} | ${message}" />

You could wrap the NLog logger or you could write an extension method so that you don't have to deal with building up a LogEventInfo object at every logging site.

For example:

public static class NLogExtensions
{
  public static void Log(this Logger logger, string task, LogLevel level, string message)
  {
    var LogEventInfo le = new LogEventInfo(level, logger.Name, message);
    le.Properties["Task"] = task;
    logger.Log(typeof(NLogExtensions), le);
  }
}

The reason for using the Log signature that takes a Type as the first parameter is to ensure that the logging of call site information work correctly.

See my answer here for a very brief discussion of "wrapping" NLog via an extension method:

Can NLog preserve callsite information through c# extension methods?

Community
  • 1
  • 1
wageoghe
  • 27,390
  • 13
  • 88
  • 116