Logging help for Multiple Clients in a single process using log4Net

3.6k Views Asked by At

I chose log4Net after much considerations to be my Logging application so please no arguments on that

OK, hear is my problem

  • I got a single process connected to multiple Clients
  • Each Client has a unique ID stored as a String in its own separate Thread
  • Each Client with the same unique ID can connect multiple times

  • I want to create a log file for every Client in a different .txt file.

  • At every new connection, i want to create a log file with client ID appended by Date Time and seconds

This scenario has got me confused since i also don't have any previous logging experience of any application at all.

I hope i have made my scenario clear enough :)

2

There are 2 best solutions below

3
On BEST ANSWER

Thank-you guys for all your answers and help but after lots and lots and lots of searching i finally found the answer .... not only i can create multiple Files but with dynamic file names.Hope this helps you guys as well :)

The idea is not to be based on a config file because as everybody said, one Appender is associated with one file name so one might be able to give one appender a dynamic file name but still not N number of File names

so my configuration is as follows

<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>

  <log4net>
  </log4net>

</configuration>

[UPDATE]: Actually you dont even need any config Yes, my configuration is empty, since i plan on to create dynamic configurations

Here is the code:

Main:

SetLevel(clientID, "ALL");
AddAppender(clientID, CreateFileAppender(clientID, fileName));

ILog log = LogManager.GetLogger(clientID);
log.Any("whatever you want");

Functions:

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 = false;

      log4net.Layout.PatternLayout layout = new
      log4net.Layout.PatternLayout();
      layout.ConversionPattern = "%d [%thread] %-5p %c [%a] - %m [%line] [%M]%n";
      layout.ActivateOptions();

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

      return appender;
}

public static void SetLevel(string loggerName, string levelName)
{
      log4net.ILog log = log4net.LogManager.GetLogger(loggerName);
      log4net.Repository.Hierarchy.Logger l = (log4net.Repository.Hierarchy.Logger)log.Logger;

      l.Level = l.Hierarchy.LevelMap[levelName];
}

// 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);
}

Now, what we do is create a logger with a specified name, and fetch it whenever and wherever we want in any thread or any class, one can even fetch a created appender and modify it if required by using the command

     log4net.LogManager.GetRepository().GetAppenders()

and iterating through it. So actually everything is Dynamic :)

Woops forgot to add in the orignal source: Log4Net Mail archive

4
On

This does not answer you question for how to write the requests to their own files, but the log4net Help specifically recommends a simpler approach. Use the ThreadContext.Properties object and other properties to decorate the log messages so that it is possible to distinguish messages from each request.

http://logging.apache.org/log4net/release/faq.html

See "Can the outputs of multiple client request go to different log files?"

If you have a Client ID, you could certainly do something like this:

log4net.ThreadContext.Properties["ClientID"] = GetTheClientId();

In your pattern layout, you can do something like this:

<conversionPattern value="%date [%thread] %-5level %logger [%property{ClientID}] - %message%newline" />

Where %property{ClientID} will pull your ClientID from the ThreadContext.

This will cause each logged message to be tagged with the relevant ClientID.

See this link for a good tutorial on using the log4net context objects:

http://www.beefycode.com/post/Log4Net-Tutorial-pt-6-Log-Event-Context.aspx

The entire log4net tutorial is very good, especially if you are just starting out with log4net.

Here is Part 1:

http://www.beefycode.com/post/Log4Net-Tutorial-pt-1-Getting-Started.aspx

Having said all of that, people do often use the GlobalContext object to name their output file, such as is described in this link:

http://geekswithblogs.net/rgupta/archive/2009/03/03/dynamic-log-filenames-with-log4net.aspx

Whenever I have seen the process of naming the output file via the GlobalContext, the suggested solution ALWAYS says to be sure to set the GlobalContext.Properties["whatever"] value BEFORE log4net actually starts. This leads me to believe that it will be difficult, if not impossible, to create individual log files based on information that is dynamically stored in the ThreadContext, since that information probably won't be known until log4net is already running.

[UPDATE]

Here is another link from here at SO that illustrates how to name the output file for a value in the GlobalContext. Note again that the value that filename is to be based on must be set into the GlobalContext before log4net is configured and before a logger is retrieved.

How do I use a GlobalContext property in a log4net appender name?

As I say above and in my comments, I am not sure that it is possible for log4net to create multiple output files (for the same FileAppender configuration) with the output filename(s) dictated by a value in the ThreadContext or by the thread id property. Maybe someone else who is more familiar with log4net can weigh in on that.

Having said that, it is possible to do exactly this in NLog. Here is an NLog configuration that defines a File Target whose name is derived, in part, from the thread id (note ${threadid} in the fileName portion of the configuration):

<targets>
    <target name="file" xsi:type="File" layout="${longdate} | ${processid} | ${threadid} | ${logger} | ${level} | ${message}" fileName="${basedir}/log_${threadid}.txt" />
</targets>

With the following code:

  class Program
  {
    public static Logger logger = LogManager.GetCurrentClassLogger();

    static void Main(string[] args)
    {

      int totalThreads = 20;
      TaskCreationOptions tco = TaskCreationOptions.None;
      Task task = null;

      logger.Info("Enter Main");

      Task[] allTasks = new Task[totalThreads];
      for (int i = 0; i < totalThreads; i++)
      {
        int ii = i;
        task = Task.Factory.StartNew(() =>
        {
          logger.Info("Inside delegate.  i = {0}", ii);
        });

        allTasks[i] = task;
      }

      logger.Info("Wait on tasks");

      Task.WaitAll(allTasks);

      logger.Info("Tasks finished");

      logger.Info("Exit Main");
    }
  }

I got 4 log files, each of whose name contains the thread id and each of which contained messages only from a single thread.

Similarly, with this configuration (note ${mdc:item=id}):

<targets>
    <target name="file" xsi:type="File" layout="${longdate} | ${processid} | ${threadid} | ${logger} | ${level} | ${message}" fileName="${basedir}/log_${mdc:item=id}.txt" />
</targets>

And this code:

  class Program
  {
    public static Logger logger = LogManager.GetCurrentClassLogger();

    static void Main(string[] args)
    {

      int totalThreads = 20;
      TaskCreationOptions tco = TaskCreationOptions.None;
      Task task = null;

      logger.Info("Enter Main");

      Task[] allTasks = new Task[totalThreads];
      for (int i = 0; i < totalThreads; i++)
      {
        int ii = i;
        task = Task.Factory.StartNew(() =>
        {
          MDC.Set("id",Thread.CurrentThread.ManagedThreadId.ToString());
          logger.Info("Inside delegate.  i = {0}", ii);
        });

        allTasks[i] = task;
      }

      logger.Info("Wait on tasks");

      Task.WaitAll(allTasks);

      logger.Info("Tasks finished");

      logger.Info("Exit Main");
    }
  }

I am able to get multiple output files based on the value stored in the MDC (the NLog equivalent to log4net's ThreadContext.Properties object).