goku_da_master
goku_da_master

Reputation: 4307

log4net logging to multiple defined appenders using class name loggers

I want to log to multiple files (2 different files only) but leave the logger name the same as the class name like is typically done:

private static readonly log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, isReadOnly);

I want to keep the class name so I can output it to the logger.

The problem is, I need to dynamically switch between appenders based on the wcf method being called (this is a wcf service). I tried various log4net config settings, including this solution:

Configure Log4net to write to multiple files

But many solutions have pre-defined logger names which I don't want. I also don't want to hard code all my different class names in the log4net config and reference those specific loggers (maintenance nightmare).

What I'm looking for is something like this:

public static ILog GetLogger(Type classType, bool shouldLogToFile2)
{
    if (shouldLogToFile2)
    {
        return LogManager.GetLogger(classType, file2Appender); // log to file2.log (this statement doesn't compile)
    }
    else
    {
        return LogManager.GetLogger(classType, file1Appender); // log to file1.log (this statement doesn't compile)
    }
}

And call it like so:

ILog log = GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, true);
log.Info("This should log to file2.log");

Upvotes: 1

Views: 1938

Answers (2)

stuartd
stuartd

Reputation: 73243

If you want to have multiple loggers with the same name but using different appenders, then you must set up multiple repositories to contain them.

As it says in the (somewhat scanty) documentation on repositories:

Named logging repositories can be created using the LogManager.CreateRepository method. The repository for can be retrieved using the LogManager.GetRepository method. A repository created in this way will need to be configured programmatically.

However, this does not mean the actual config must be in code, but that you should do something like this:

// or wherever
string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; 

var repository = log4net.LogManager.CreateRepository("file1repo");
log4net.XmlConfigurator.ConfigureAndWatch(repository, new FileInfo(configPath));

repository = log4net.LogManager.CreateRepository("file2repo");
log4net.XmlConfigurator.ConfigureAndWatch(repository, new FileInfo(configPath));

Then you would write code to amend the 'file2' appender to write to file2:

var appender = LogManager.GetRepository("file2repo").GetAppenders()
                         .OfType<RollingFileAppender>().Single();

appender.File = "file2.log";
appender.ActivateOptions(); 

Your code then looks like this:

public static ILog GetLogger(Type classType, bool shouldLogToFile2)
{
    return LogManager.GetLogger(shouldLogToFile2 ? "file2repo" : "file1repo", classType);
}

Upvotes: 1

goku_da_master
goku_da_master

Reputation: 4307

After playing around with it for a few hours, here's what I came up with.

The idea is to prefix each logger name with "File2" for those loggers that should append to file2.log. Then return the appropriate logger you're looking for based on the boolean that's passed in.

To prevent the log message from being written to both files, I decided to pull all the loggers out of the root logger (see config) and to explicitly use each appender in code.

The config:

<log4net>       
    <appender name="File1Appender" type="log4net.Appender.RollingFileAppender">
      <file value="c:\file1.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="50MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5p %logger %message%newline" />
      </layout>
    </appender>

    <appender name="File2Appender" type="log4net.Appender.RollingFileAppender">
      <file value="c:\file2.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="50MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5p %logger %message%newline" />
      </layout>
    </appender>

    <root>
      <level value="ALL" />      
    </root>
    <logger name="LoggerF1">
      <appender-ref ref="File1Appender" />
    </logger>
    <logger name="LoggerF2">
      <appender-ref ref="File2Appender" />
    </logger>
</log4net>

And the code:

public static ILog GetLogger(Type classType, bool shouldLogToFile2 )
{
    ILog newLogger = log4net.LogManager.GetLogger(shouldLogToFile2 ? "File2." + classType.ToString() : classType.ToString());
    Logger loggerInstance = (Logger) newLogger.Logger;
    // if the newLogger doesn't have an appender (which it won't the first time it's created) then add the appropriate appender
    if (loggerInstance.Appenders.Count == 0)
    {           
        IAppender[] appenders = LogManager.GetRepository().GetAppenders();
        IAppender appender = appenders.SingleOrDefault(a => a.Name == (shouldLogToFile2 ? "File2Appender" : "File1Appender"));
        loggerInstance.AddAppender(appender);           
    }
    return newLogger;
}

This way it's all done in the config, and there's no duplicating the config in code. I wanted the same log level used for both appenders which is why I put it in the root logger instead of in each individual logger.

Upvotes: 1

Related Questions