I know the question is old, but apparently there is still no out-of-the-box solution for this, so here is a custom logger that works for me (inspired from https://github.com/ramonsmits/common-logging but I changed almost everything in the end). Tested with Common.Logging version 3.1.0.
The FactoryAdapter
/// <summary>
/// Adapter hub for Common.Logging that can send logs to multiple other adapters
/// </summary>
public class MultiLoggerFactoryAdapter : AbstractCachingLoggerFactoryAdapter
{
private readonly List<ILoggerFactoryAdapter> LoggerFactoryAdapters;
/// <summary>
/// Initializes a new instance of the <see cref="MultiFactoryLoggerFactoryAdapter"/> class.
/// </summary>
public MultiLoggerFactoryAdapter(CommonLogging.Configuration.NameValueCollection properties)
{
LoggerFactoryAdapters = new List<ILoggerFactoryAdapter>();
foreach(var factoryAdapter in properties.Where(e => e.Key.EndsWith(".factoryAdapter")))
{
string adapterName = factoryAdapter.Key.Substring(0, factoryAdapter.Key.Length - 15);
string adapterType = factoryAdapter.Value;
var adapterConfig = new CommonLogging.Configuration.NameValueCollection();
foreach(var entry in properties.Where(e1 => e1.Key.StartsWith(adapterName + ".")))
{
adapterConfig.Add(entry.Key.Substring(adapterName.Length + 1), entry.Value);
}
var adapter = (ILoggerFactoryAdapter)Activator.CreateInstance(Type.GetType(adapterType), adapterConfig);
LoggerFactoryAdapters.Add(adapter);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="MultiFactoryLoggerFactoryAdapter"/> class.
/// </summary>
/// <param name="factoryAdapters">The factory adapters.</param>
public MultiLoggerFactoryAdapter(List<ILoggerFactoryAdapter> factoryAdapters)
{
LoggerFactoryAdapters = factoryAdapters;
}
protected override ILog CreateLogger(string name)
{
var loggers = new List<ILog>(LoggerFactoryAdapters.Count);
foreach (var f in LoggerFactoryAdapters)
{
loggers.Add(f.GetLogger(name));
}
return new MultiLogger(loggers);
}
}
The logger
/// <summary>
/// Adapter hub for Common.Logging that can send logs to multiple other adapters
/// </summary>
public class MultiLogger : AbstractLogger
{
private readonly List<ILog> Loggers;
public static readonly IDictionary<LogLevel, Action<ILog, object, Exception>> LogActions = new Dictionary<LogLevel, Action<ILog, object, Exception>>()
{
{ LogLevel.Debug, (logger, message, exception) => logger.Debug(message, exception) },
{ LogLevel.Error, (logger, message, exception) => logger.Error(message, exception) },
{ LogLevel.Fatal, (logger, message, exception) => logger.Fatal(message, exception) },
{ LogLevel.Info, (logger, message, exception) => logger.Info(message, exception) },
{ LogLevel.Trace, (logger, message, exception) => logger.Trace(message, exception) },
{ LogLevel.Warn, (logger, message, exception) => logger.Warn(message, exception) },
};
/// <summary>
/// Initializes a new instance of the <see cref="MultiLogger"/> class.
/// </summary>
/// <param name="loggers">The loggers.</param>
public MultiLogger(List<ILog> loggers)
{
Loggers = loggers;
}
public override bool IsDebugEnabled { get { return Loggers.Any(l => l.IsDebugEnabled); } }
public override bool IsErrorEnabled { get { return Loggers.Any(l => l.IsErrorEnabled); } }
public override bool IsFatalEnabled { get { return Loggers.Any(l => l.IsFatalEnabled); } }
public override bool IsInfoEnabled { get { return Loggers.Any(l => l.IsInfoEnabled); } }
public override bool IsTraceEnabled { get { return Loggers.Any(l => l.IsTraceEnabled); } }
public override bool IsWarnEnabled { get { return Loggers.Any(l => l.IsWarnEnabled); } }
protected override void WriteInternal(LogLevel level, object message, Exception exception)
{
List<Exception> exceptions = null;
foreach(var logger in Loggers)
{
try
{
LogActions[level](logger, message, exception);
}
catch(Exception e)
{
if(exceptions == null)
exceptions = new List<Exception>();
exceptions.Add(e);
}
}
if(exceptions != null)
throw new AggregateException("One or more exceptions occured while forwarding log message to multiple loggers", exceptions);
}
}
And you can configure it like this:
<common>
<logging>
<factoryAdapter type="MultiLoggerFactoryAdapter, YourAssemblyName">
<arg key="Log4Net.factoryAdapter" value="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net1213" />
<arg key="Log4Net.configType" value="INLINE" />
<arg key="Debug.factoryAdapter" value="Common.Logging.Simple.TraceLoggerFactoryAdapter, Common.Logging" />
</factoryAdapter>
</logging>
</common>
That is, for each logger, you add a line with key LoggerName.factoryAdapter
, and then you can add properties for this logger by using the same name for the key, such as LoggerName.someProperty
.