Reputation: 4307
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
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 theLogManager.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
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