3

I have a program that starts smaller programs, or tasks, and now I want each task to log to their own filename dynamically, preferrably using the name of task. I've digged around a little and it seems that there's a nifty %property thing that you can use with Log4Net. So I tried that. But it seems that Log4Net doesn't pickup changes for this after it has been initialized.

I wrote a little test program to play around with. I would prefer to only build the windsor container once, but then Log4Net won't be "reinitialized", and create a new logfile. So now I have to build the container before starting the task to be sure that I get the right filename on the logfile.

Does anyone know if there's a better/smarter way to change the filename of the log during runtime?

Here's my testcode:

public class Program
{
    static void Main(string[] args)
    {
        GlobalContext.Properties["LogName"] = "default";
        XmlConfigurator.ConfigureAndWatch(new FileInfo("log4net.config"));

        while (true)
        {
            Console.WriteLine();
            Console.Write("> ");
            string cmd = (Console.ReadLine() ?? string.Empty);
            if (string.IsNullOrWhiteSpace(cmd)) continue;
            if (cmd.ToLower() == "exit") Environment.Exit(0);

            ExecuteTask(Regex.Split(cmd, @"\s+"));
        }
    }

    private static void ExecuteTask(string[] args)
    {
        //This changes the filename Log4Net logs to, but Log4Net only picks it up if I rebuild my container
        GlobalContext.Properties["LogName"] = args[0];

        //This should only be run once. No real need to build the container before each run, except to reinitialize Log4Net
        var container = BuildContainer();
        try
        {
            var task = container.Resolve<ITask>(args[0]);
            task.DoLog();
        }
        catch (Exception)
        {
            // This doesn't work as expected, Log4Net logs to the file specified outside of the trycatch
            GlobalContext.Properties["LogName"] = "default";
            var logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
            logger.Info("Could not find "+args[0]);
        }
    }


    private static WindsorContainer BuildContainer()
    {
        Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);

        var container = new WindsorContainer();
        container.Install(
            FromAssembly.This());

        return container;
    }
}

public interface ITask
{
    void DoLog();
}

public class TwoTask : ITask
{
    private readonly ILogger _logger;

    public TwoTask(ILogger logger)
    {
        _logger = logger;
    }

    public void DoLog()
    {
        _logger.Info("Hello!");
    }
}

public class OneTask : ITask
{
    private readonly ILogger _logger;

    public OneTask(ILogger logger)
    {
        _logger = logger;
    }

    public void DoLog()
    {
        _logger.Info("Hello!");
    }
}

public class Installer : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
                                  .BasedOn(typeof(ITask))
                                  .Configure(c => c.LifeStyle.Transient.Named(c.Implementation.Name)));
        container.AddFacility<LoggingFacility>(f => f.UseLog4Net("log4net.config"));
    }
}

Nugets used: Castle.Core, Castle.Core-log4net, Castle.LoggingFacility, Castle.Windsor

Markus
  • 1,614
  • 1
  • 22
  • 32

2 Answers2

1

I want each task to log to their own filename dynamically, preferably using the name of task

The only way to do this - assuming your tasks run concurrently - is to have one logger per task, where each logger has it's own appender.

As you have noticed, changing the property doesn't change the log file, as that's set at configuration time. A common solution is to patch the appender file name programmatically at run time - which is easy enough, but then because loggers share appenders, you would see one task logging into another task's file.

So you're going to have to construct loggers and appenders at runtime - excerpted from that post:

// Create a new file appender
public static log4net.Appender.IAppender CreateFileAppender(string name,
string fileName)
{
    log4net.Appender.FileAppender appender = new
    log4net.Appender.FileAppender();
    appender.Name = name;
    appender.File = fileName;
    appender.AppendToFile = true;

    log4net.Layout.PatternLayout layout = new
    log4net.Layout.PatternLayout();
    layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
    layout.ActivateOptions();

    appender.Layout = layout;
    appender.ActivateOptions();

    return appender;
}

And:

// Add an appender to a logger
public static void AddAppender(string loggerName,log4net.Appender.IAppender appender)
{
     log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
     log4net.Repository.Hierarchy.Logger l =
          (log4net.Repository.Hierarchy.Logger)log.Logger;

      l.AddAppender(appender);
}
stuartd
  • 70,509
  • 14
  • 132
  • 163
  • The tasks won't run in parallell, so there should not be a problem with them writing in each other's files. If I just could get Castle.Windsor to resolve just the logger before each run I think it would be ok. But that doesn't seem to work either. – Markus Aug 26 '16 at 09:46
  • 1
    In which case, you can just set the filename programmatically for each task and log4net will create a new file - e.g. http://stackoverflow.com/a/32873679/43846 – stuartd Aug 26 '16 at 09:53
-1

In your appenders defined in log4net.config you can specify an output format for the file name. For instance <file value="fixedName"/>. You can also have a dynamic file name by using <staticLogFileName value="false"/> together with a pattern, e.g.

<file type="log4net.Util.PatternString" value="somePrefix-%property{SomePropertyName}-%utcdate{yyMMdd}.trace.log" />. 

This will route to different files based on date and your "SomePropertyName" property.

You will have to set that propery in your tasks, like log4net.ThreadContext.Properties["SomePropertyName"] = "someValue";.

I guess a good switch would be the name of the thread that generated the logging event (%t or %thread). Outputs the thread number if no name is available. But you could set the thread name explicitly in your ITasks.

andrei.ciprian
  • 2,895
  • 1
  • 19
  • 29
  • 1
    All you've done really is used `ThreadContext` instead of `GlobalContext` but this still won't work - the file name is set at configuration time. You can change the value of property as much as you want, but it will have no effect. – stuartd Aug 25 '16 at 10:33