Unique log file for each instance of class

2019-01-22 08:12发布

问题:

I am currently running a windows service that creates multiple instances of a class.

At the top of the service class and every other class in my solution, I have something like this:

private static readonly ILog _log = LogManager.GetLogger(typeof(SomeClassTypeHere));

In my App.config, I have Log4Net configured for a single file:

<log4net debug="true">
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="Logs\SomeLogFileName.xml" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <countDirection value="1" />
      <maxSizeRollBackups value="30" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.XmlLayoutSchemaLog4j">
        <locationInfo value="true" />
      </layout>
    </appender>

    <root>
      <level value="INFO" />
      <appender-ref ref="RollingFileAppender" />
    </root>
  </log4net>

This works great in most respects, and everything logs to a single file. However, I'd really like to create a separate log file for each instance of a particular class that my service creates.
This is a class that we often need to monitor for support and we can have a handful of instances running at the same time.
We don't know which instances will be running at a given time, so it makes creating static files in the configuration kinda painful.

I tried taking off the readonly modifier and setting the following in the class constructor:

_log = LogManager.GetLogger("DataCollectionClass_" + deviceName + "_" + DateTime.Now.ToString("MMddyyyy"), typeof(SomeClassTypeHere));

But that requires that I define an appender manually in the configuration, which would be cumbersome and tough to keep up with.

Any thoughts on doing this in L4N? I have seen links here but don't really know if that much frameworking is necessary.

回答1:

The code below shows how you can programatically configure log4Net without using a configuration file to achieve the effect you're looking for. Basically, it just involves creating a named logger and adding to the hierarchy.

I used as a starting point one of the answers from here.

using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;

namespace LoggerTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DeviceConnection dev1 = new DeviceConnection("Device1");
            DeviceConnection dev2 = new DeviceConnection("Device2");

            dev1.DoSomething();
            dev2.DoSomething();
        }
    }

    public class DeviceConnection
    {
        private string name;
        private readonly ILog logger;

        public DeviceConnection(string _name)
        {
            name = _name;

            logger = TestLogger.AddNamedLogger(name);

            logger.Info("----  Begin Logging for DeviceConnection: " + name);
        }

        public void DoSomething()
        {
            logger.Info("Doing something for device connection " + name);
        }
    }

    public static class TestLogger
    {
        private static PatternLayout _layout = new PatternLayout();
        private const string LOG_PATTERN = "%d [%t] %-5p %m%n";

        public static string DefaultPattern
        {
            get { return LOG_PATTERN; }
        }

        static TestLogger()
        {
            _layout.ConversionPattern = DefaultPattern;
            _layout.ActivateOptions();

            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
            hierarchy.Configured = true;
        }

        public static PatternLayout DefaultLayout
        {
            get { return _layout; }
        }

        public static ILog AddNamedLogger(string name)
        {
            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
            Logger newLogger = hierarchy.GetLogger(name) as Logger;

            PatternLayout patternLayout = new PatternLayout();
            patternLayout.ConversionPattern = LOG_PATTERN;
            patternLayout.ActivateOptions();

            RollingFileAppender roller = new RollingFileAppender();
            roller.Layout = patternLayout;
            roller.AppendToFile = true;
            roller.RollingStyle = RollingFileAppender.RollingMode.Size;
            roller.MaxSizeRollBackups = 4;
            roller.MaximumFileSize = "100KB";
            roller.StaticLogFileName = true;
            roller.File = name + ".log";
            roller.ActivateOptions();

            newLogger.AddAppender(roller);

            return LogManager.GetLogger(name);
        }
    }
}


回答2:

Use the ADO.Net appender and log to a SQL Server database and just query for the information you need.

Another alternative is the log4net Dashboard: http://www.l4ndash.com/. It does a pretty decent job of integrating logs from various sources, and then slicing and dicing them in different ways. Reasonbly priced, too.



回答3:

log4net has a concept called logger hierarchy.

A logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. A logger is said to be a parent of a child logger if there are no ancestors between itself and the descendant logger. The hierarchy works very much in the same way as the namespace and class hierarchy in .NET. This is very convenient as we shall soon see.

So you really should be creating your instance specific loggers with . characters instead of _ characters.

_log = LogManager.GetLogger("DataCollectionClass." + deviceName + "." + DateTime.Now.ToString("MMddyyyy"), typeof(SomeClassTypeHere));

Then in the configuration file reference the logger hierarchy like the following.

<log4net>
  <!-- Other log4net configuration omitted for brevity -->
  <logger name="DataCollectionClass">
    <!-- Put your appender-ref entries here -->
  </logger>
</log4net>

Notice how the logger name reference does not contain the fully qualified name used in code. It only references the root of the name. Think of it in the same way you think of namespaces.



回答4:

I have an article that might help:

http://horth.com/blog/?p=165

This is about changing a logfile at runtime. What you could do is pass in the file name for each instance into your log4net file. That way you could create a log file for each instance of your class. This way your config file is simple and yet you have the flexibility to create a new log file for each class instance.

It was mentioned above that you could log to a database as well with indicators for each instance. If you don't wan to buy anything, use SQL Express which allows 10GB databases. You can even write directly to the MDF file instead of installing SQL.