log4netlog4net-appender

Log4Net Separate Configurations per Addin


I am using Log4Net for several pieces of software I'm writing, all of what are addins to a 'parent' software. It is logging, however the log files are getting intermixed... I would like to have a file appender that logs to files that are unique per application and session. Right now I'm doing something like this:

 foreach (ILog log in LogManager.GetCurrentLoggers())
        {
            var logger = (Logger)log.Logger;
            logger.Level = wFSettings.CommonSettings.LogLevel;
            logger.RemoveAllAppenders();

            var ap = new FileAppender()
            {
                Name = "fileWF",
                File = _logPath,
                AppendToFile = true,
                ImmediateFlush = true,
                LockingModel = new FileAppender.MinimalLock(),
                Threshold = Level.All,
            };
            var lp = new PatternLayout() { ConversionPattern = "%date [%thread] %level %logger - %message%newline" };
            lp.ActivateOptions();
            ap.Layout = lp;
            ap.ActivateOptions();
            logger.AddAppender(ap);
            logger.Repository.Configured = true;
        }

That runs after the settings for the program are read (basically the first thing it does) so that the log level and file path are correct per application settings. However as you can see it just removes all appenders and adds its own to each thing. I'm thinking that is why it's intermixing logs; basically the last one wins.

Anyway I have searched around and I'm wondering if I need to customize the repository somehow? I couldn't find anything pointing to how to do it though.

Any pointers would be greatly appreciated.


Solution

  • For anyone wondering that comes across this, I ended up solving this by creating appenders with names. Here is the configuration method for one addin:

        public static void ConfigureLogger(Logger logger)
        {
            // Set actual logger to all and filter appenders
            logger.Level = Level.All;
            var ap = logger.Appenders.ToArray().FirstOrDefault(x => x.Name == "fileWF");
    
            if (ap != null)
            {
                logger.Appenders.Remove(ap);
            }
    
            var fa = new FileAppender()
            {
                Name = "fileWF",
                File = _logPath,
                AppendToFile = true,
                ImmediateFlush = true,
                LockingModel = new FileAppender.MinimalLock(),
                Threshold = wFSettings.CommonSettings.LogLevel
            };
            var lp = new PatternLayout() { ConversionPattern = "%date [%thread] %level %logger - %message%newline" };
            lp.ActivateOptions();
            fa.Layout = lp;
            fa.ActivateOptions();
            logger.AddAppender(fa);
    
    #if DEBUG
            var ta = new TraceAppender()
            {
                Name = "traceWF",
                ImmediateFlush = true,
                Threshold = wFSettings.CommonSettings.LogLevel,
                Layout = lp
            };
            logger.AddAppender(ta);
    #endif
            logger.Repository.Configured = true;
        }
    

    So basically it's creating appenders with a specific name (in this case 'fileWF') and adds them to the logger, that appender writes to the file for that addin. On startup and when the logging level is changed in the application it runs this:

        internal static void UpdateLogConfig()
        {
            if (wFSettings.CommonSettings == null) return;
            if (!string.IsNullOrEmpty(wFSettings.CommonSettings.CustomLoggerConfig) && !_customLoggerConfigured)
            {
                XmlConfigurator.Configure(new Uri(wFSettings.CommonSettings.CustomLoggerConfig));
                _customLoggerConfigured = true;
                return;
            }
    
            //No custom config, construct default
            if (string.IsNullOrEmpty(_logPath))
            {
                _logPath = Path.Combine(Props.LogFolder,
                                        "WF Log - " + DateTime.Now.ToString("yyyy-MM-dd_hh_mm_ss") + ".log");
            }
    
            var commonNamespaces = new List<string>()
            {
                "rdes.license.core",
                "rd.utility",
                "rd.datamodel",
                "rdrestconsumer",
                "rdes.common",
                "rdfilewatcher"
            };
    
            var ns = typeof(WfUtility).Namespace?.Replace("Shared", string.Empty);
    
            foreach (ILog log in LogManager.GetCurrentLoggers())
            {
                var logger = (Logger)log.Logger;
    
                // Ignore KM and SM
                if (ns != null && !logger.Name.StartsWith(ns) &&
                    !commonNamespaces.Any(x => logger.Name.ToLower().StartsWith(x))) continue;
    
                // Set actual logger to all and filter appenders
                ConfigureLogger(logger);
            }
        }
    

    Basically it looks through the loggers (which are named by the type they log for) and skips any that aren't in the 'common' namespaces that I use or the same project level namespace as the utility where this runs. For the others it runs the configuration that makes sure the appender for this addin is connected. If you didn't want to capture logs from those common libraries you could skip that part, but I'm basically filtering to only the namespaces I want to log and instead of removing all appenders and then adding mine I'm just removing and adding the appenders I'm interested in (that way if another addin is also logging from the shared library it can show up in both).

    Seems a little complicated but it seems to be working. Still would be open to suggestions for improvements.