1

I have a project which already uses NLog . The application creates an instance of a given class passes an instance of ILogger to it and then calls an "execute" method within the instance - The system is multi threaded and can have up to 200 of these instances running at once, the log files can get big and difficult to read.

We have a requirement to create a new log file per instance of a class and store it with the output of that instance (they each have a unique identifier)

My question is: is it possible to have NLog create a new file per instance of a class? I know you can have different log files for different classes, what I want is a different log file for each instance of a class.

I have looked online but cant find much information on this, I dont want to reinvent the wheel by creating my own logging class if its already catered for in NLog.

Below is a console app that I have knocked up to demonstrate what happens when reconfiguring the existing loggers - essentially it changes all instances of the logger

class Program
{
    static void Main(string[] args)
    {
        BackgroundWorker bw1 = new BackgroundWorker();
        bw1.DoWork += Bw1_DoWork;
        bw1.RunWorkerAsync();

        BackgroundWorker bw2 = new BackgroundWorker();
        bw2.DoWork += Bw2_DoWork;
        bw2.RunWorkerAsync();

        Console.ReadLine();
    }

    private static void Bw1_DoWork(object sender, DoWorkEventArgs e)
    {
        workLoad one = new workLoad("One");
        one.execute();
    }

    private static void Bw2_DoWork(object sender, DoWorkEventArgs e)
    {
        workLoad one = new workLoad("Two");
        one.execute();
    }

}

public class workLoad
{
    private ILogger _logger { get; set; }
    private string _number { get; set; }
    public workLoad(string number)
    {
        _logger = LogManager.GetCurrentClassLogger();
        _number = number;

        var target = (FileTarget) LogManager.Configuration.FindTargetByName("DebugFile");
        target.FileName = $"c:\\temp\\File{number}.txt";
        LogManager.ReconfigExistingLoggers();
    }

    public void execute()
    {
        for (int i = 0; i < 1000; i++)
        {
            _logger.Info(_number + " LOOPING" + i);        
        }

    }
}

this results in all output going to one file (two.txt) a sample of the log file is below.

2017-06-13 17:00:42.4156 TestNlog.workLoad Two LOOPING0 2017-06-13 17:00:42.4156 TestNlog.workLoad One LOOPING0 2017-06-13 17:00:42.4806 TestNlog.workLoad One LOOPING1 2017-06-13 17:00:42.4746 TestNlog.workLoad Two LOOPING1 2017-06-13 17:00:42.4806 TestNlog.workLoad One LOOPING2 2017-06-13 17:00:42.4806 TestNlog.workLoad Two LOOPING2 2017-06-13 17:00:42.4946 TestNlog.workLoad One LOOPING3 2017-06-13 17:00:42.4946 TestNlog.workLoad Two LOOPING3 2017-06-13 17:00:42.4946 TestNlog.workLoad One LOOPING4 2017-06-13 17:00:42.4946 TestNlog.workLoad Two LOOPING4 2017-06-13 17:00:42.5132 TestNlog.workLoad One LOOPING5 2017-06-13 17:00:42.5132 TestNlog.workLoad Two LOOPING5 2017-06-13 17:00:42.5132 TestNlog.workLoad One LOOPING6 2017-06-13 17:00:42.5257 TestNlog.workLoad Two LOOPING6 2017-06-13 17:00:42.5257 TestNlog.workLoad One LOOPING7 2017-06-13 17:00:42.5257 TestNlog.workLoad Two LOOPING7 2017-06-13 17:00:42.5407 TestNlog.workLoad Two LOOPING8 2017-06-13 17:00:42.5407 TestNlog.workLoad One LOOPING8 2017-06-13 17:00:42.5407 TestNlog.workLoad Two LOOPING9 2017-06-13 17:00:42.5407 TestNlog.workLoad One LOOPING9 2017-06-13 17:00:42.5577 TestNlog.workLoad Two LOOPING10 2017-06-13 17:00:42.5577 TestNlog.workLoad One LOOPING10

Ideally Id be looking for everything from instance one to go into One.TXT and everything from instance two to be in two.txt (You can see how having 200+ running like this might be a problem!)

Julian
  • 33,915
  • 22
  • 119
  • 174
5NRF
  • 401
  • 3
  • 12

4 Answers4

4

Maybe try using ${logger} in the fileName (Requires NLog 4.5 for NLog File Archive Logic to work correctly):

<targets>
    <target xsi:type="File" name="DebugFile"
         fileName="${basedir}\logs\${logger:shortName=true}.txt" />
</targets>

Then the filename will match the logger-name. When creating the logger then instead of doing this:

_logger = LogManager.GetCurrentClassLogger();

Then do it like this:

_logger = LogManager.GetCurrentClassLogger(typeof(workLoad).ToString() + "." + number);

Alternative you create the Loggers like this (not tested if it works):

private static object _lock = new object();

public static NLog.Logger PrepareLogger(string number)
{
        lock (_lock)
        {
            var fileTarget = LogManager.Configuration.FindTargetByName(number);
            if (fileTarget == null)
            {
                var fileTarget = new FileTarget(number);
                fileTarget.FileName = $"c:\\temp\\File{number}.txt";

                var template = LogManager.Configuration.FindTargetByName("DebugFile");
                if (template != null)
                {
                      fileTarget.Layout = template.Layout;
                }
                LogManager.Configuration.AddTarget(fileTarget);
                var rule = new LoggingRule(number, LogLevel.Debug, fileTarget) { Final = true };
                LogManager.Configuration.LoggingRules.Add(rule);
                LogManager.ReconfigExistingLoggers();
            }
        }
    return LogManager.GetLogger(number);
}
Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70
1

You can create your own targets at runtime or add a parameter to the layout and pass it through.

Create Custom Log Files

    /// <summary>
    /// Creates the custom log file.
    /// Logging of Info and Warning Message Only.
    /// </summary>
    /// <param name="TargetName">Name of the target.</param>
    /// <param name="TargetFileName">Name of the target file.</param>
    /// <param name="LoggerName">Name of the logger.</param>
    public void CreateCustomLogFile(String TargetName, String TargetFileName, String LoggerName = "*")
    {
        try
        {
            var NLogTarget = LogManager.Configuration.FindTargetByName(TargetName);

            if (NLogTarget == null) //Don't Re Add the Target
            {
                NLogTarget = new FileTarget()
                {
                    Name = TargetName,
                    FileName = TargetFileName,
                    Layout = @"[${date:format=yyyy-MM-dd HH\:mm\:ss}] ${message}"
                };

                LogManager.Configuration.AddTarget(TargetName, NLogTarget);
            }

            var NLogRule = new LoggingRule(LoggerName, NLogTarget);
            NLogRule.EnableLoggingForLevel(LogLevel.Info);
            NLogRule.EnableLoggingForLevel(LogLevel.Warn);
            LogManager.Configuration.LoggingRules.Add(NLogRule);

            LogManager.ReconfigExistingLoggers();
        }
        catch { }
    }

    /// <summary>
    /// Removes the custom log file.
    /// </summary>
    /// <param name="TargetName">Name of the target.</param>
    public void RemoveCustomLogFile(String TargetName)
    {
        try
        {
            if (LogManager.Configuration.FindTargetByName(TargetName) != null)
            {
                var Target = LogManager.Configuration.FindTargetByName(TargetName);

                foreach (var rule in LogManager.Configuration.LoggingRules)
                {
                    rule.Targets.Remove(Target);
                }
                LogManager.Configuration.RemoveTarget(TargetName);
                Target.Dispose();
                LogManager.ReconfigExistingLoggers();
            }
        }
        catch { }
    }

or Add a parameter to the NLog Layout

<target name="CTSCoreByPortNumber" xsi:type="File" 
     fileName="${logDirectory}/CTS${event-context:item=PortNumber}.log"
     layout="${fulllayout}" />

Example function to write to the correct log.

    /// <summary>
    /// WriteLog - Overloaded Method to write a Message. 
    /// </summary>
    /// <param name="LogType">LogLevel.Trace
    /// LogLevel.Debug,
    /// LogLevel.Info,
    /// LogLevel.Warn,
    /// LogLevel.Error,
    /// LogLevel.Fatal,</param>
    /// <param name="Msg">Message to write</param>
    /// <param name="args">Arguments</param>
    public void WriteLog(LogLevel LogType, String Msg, object[] args)
    {
        try
        {
            if (args == null)
                args = new object[0];

            LogEventInfo logEvent = new LogEventInfo(LogType, GetCallingMethodName(), null, String.Format(Msg, args), null);

            if (PortNumber != 0)
                logEvent.Properties["PortNumber"] = ".Line_" + PortNumber;

            Instance.Log(typeof(Logging), logEvent);

            //Instance.Log((LogLevel)LogType, Msg, args);
        }
        catch (Exception) { throw; }
    }

PortNumber in the Target is set by the Properties code before logging.

JAZ
  • 1,050
  • 6
  • 15
0

maybe you could make the constructor of the class initialize a new log

0

Try ReconfigExistingLoggers, might be your best bet.

<targets>
    <target xsi:type="File"
        name="Foo"
        fileName="${basedir}/logs/logfile.txt"
        keepFileOpen="false"
        encoding="iso-8859-2" />
</targets>

Then

var target = (FileTarget)LogManager.Configuration.FindTargetByName("Foo");
target.FileName = $"{change_my_location}/class_instance.txt";
LogManager.ReconfigExistingLoggers();

From this answer here: Update NLog target filename at runtime

Kyle
  • 5,407
  • 6
  • 32
  • 47
  • Doesnt work, changes the log file for ALL instances of ILogger using that target which doesnt work. Ill edit the question now with the code that I mocked up your suggestion in to show the kind of thing that I mean. – 5NRF Jun 13 '17 at 15:57
  • How many instances can run in parallel? 200? Why not a db target? You can do `SELECT * FROM logs WHERE instance = 'blah'` – Kyle Jun 13 '17 at 16:20
  • Can be up to 250 - in practice it will be around 25 at a time. Targeting the DB is a good idea, I hadnt thought of that - Ill treat that as a last resort - I'd really like to get it working with a file though that way the output of the process and the logs can all be stored together... Its also what the users are used to. Good thinking though - its definitly going on the "maybe" pile :) – 5NRF Jun 13 '17 at 16:46