I am currently working on moving a large project over to use Common.Logging and I was hoping to be able to output logging events to more than one 3rd party logging implementation.
We currently have an internally developed tracing library that we would like to keep using for tracing and debugging messages. I would also like to start using log4net to sent some messages to a database for reporting or send email notifications at some levels.
What I'm looking for is something like this:
<common>
<logging>
<factoryAdapter type="CustomTraceFactoryAdapter, MyAssembly">
<arg key="configType" value="INLINE"/>
</factoryAdapter>
<factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net">
<arg key="configType" value="INLINE"/>
</factoryAdapter>
</logging>
</common>
Is there any way to do this with the out of the box configuration options?
AFAIK, nothing exists in Common.Logging for what you need. Here are a couple of alternatives.
1) Develop a couple of custom logger factory adaptor as described at http://netcommon.sourceforge.net/docs/2.1.0/reference/html/ch01.html#logging-advanced-customfactoryadapter. One custom factory, which it appears you have already developed, would be for your internal tracing library. The second custom factory would create composite logger objects by combining loggers from one or more other factories.
2) Develop a custom log4net appender (http://logging.apache.org/log4net/release/faq.html#custom-appender) for your tracing library and configure Common.Logging to only use the log4net factory.
I feel the second option would be easiest as it would not involve changing the configuration behavior of Common.Logging so it can constitute the composite factory from other configured factories.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With