I've scoured through Stack Overflow to get an idea on how I can have logging in my C# application and keep the usage requirements specific to my application. The following questions that have previously been answered have helped assist me:
These implementations seem to want me to pass log4net.ILog to the constructor of my implementation or to the base implementation of log4net's LogImpl. However, I was having trouble configuring my abstract logger using Simple Injector.
As I see it, my implementation is working really well, but I dont know what some draw backs might exist, or perhaps other ways of doing this.
What I've Got So Far
ILogger
interface that requires a void Log(LogEntry entry)
method. public class Log4netAdapter<T> : ILogger
Source Code
Simple Injector DI Container:
private SimpleInjector.Container container;
[SetUp]
public void SetUp()
{
// init log4net
XmlConfigurator.Configure();
container = new SimpleInjector.Container();
container.RegisterConditional(
typeof(ILogger),
c => typeof(Log4netAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
c => true);
}
Logger interface:
public interface ILogger
{
void Log(LogEntry entry);
}
public class Log4netAdapter<T> : ILogger
{
private readonly log4net.ILog Logger;
public Log4netAdapter()
{
this.Logger = LogManager.GetLogger(typeof(T));
}
public void Log(LogEntry entry)
{
if (entry.Severity == LoggingEventType.Debug)
Logger.Debug(entry.Message, entry.Exception);
else if (entry.Severity == LoggingEventType.Information)
Logger.Info(entry.Message, entry.Exception);
else if (entry.Severity == LoggingEventType.Warning)
Logger.Warn(entry.Message, entry.Exception);
else if (entry.Severity == LoggingEventType.Error)
Logger.Error(entry.Message, entry.Exception);
else
Logger.Fatal(entry.Message, entry.Exception);
}
}
Extensions for ILogger
:
public static class LoggerExtensions
{
public static void Log(this ILogger logger, string message)
{
logger.Log(new LogEntry(LoggingEventType.Information, message));
}
public static void Log(this ILogger logger, Exception exception)
{
logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
}
}
What I want to know
I want to know if this follows SOLID principles and is there a better way? If there is a better way, can anyone provide me with the reason for it following an example via C#.
What I dont like
My Implementation does not allow me to just call a specific method on any class that I am passing Ilogger
into the constructor. It requires me to have Extensions created on the ILogger
interface, which then redirects to my Log4netAdapter
.
I want to know if this follows SOLID principles and is there a better way?
Whether or not this is SOLID, highly depends on the broader context of the application. For instance, when you are injecting your ILogger
into a large number of classes in your system, you are likely violating the Single Responsibility Principle and Open/Closed Principle. For instance, see this q&a.
Although analysis of SRP and OCP require more context, we can actually say something about the ISP and DIP here:
ILogger
interface defines a single member and, therefore, follows the Interface Segregation Principle, which states that abstractions should be narrow.ILogger
abstraction and, therefore, follows the Dependency Inversion Principle, which states that abstractions should be owned by the consumers of the abstraction.What I dont like ... It requires me to have Extensions created on the ILogger interface, which then redirects to my Log4netAdapter.
When we apply the Dependency Inversion Principle (mostly through Dependency Injection) we separate dependencies into two distinct groups:
Where a class can safely directly depend on and invoke any Stable Dependency, you wish to hide any Volatile Dependency behind an abstraction.
In the context of your question, Log4Net's appenders are, from the context of your application's code, Volatile Dependencies. This is especially because they do I/O, or as DIPP&P puts it:
These are two characteristics of Volatile Dependencies and to make our application maintainable and testable, we hide Volatile Dependencies behind an abstraction, hence your ILogger
abstraction.
The extension methods on ILogger
, however, are not Volatile Dependencies; they are Stable Dependencies. This is because the behavior inside those extension methods:
The volatile part of the logging behavior is completely hidden behind the ILogger
interface and this allows you to replace, mock, and intercept all volatile behavior.
Because the extension methods are Stable Dependencies, any consumer can safely depend on them, without causing any maintainability or testability issues. As a matter of fact, having this stable part of the logging behavior not hidden behind an abstraction has some interesting advantages:
This does mean, though, that you should ensure that the extension methods stay stable; they should not start to contain nondeterministic or volatile behavior.
It requires me to have Extensions created on the ILogger interface
The extension methods are not part of the interface; they are part of the consuming code. This means you can place the extension methods everywhere you like, and could even have different extension methods for different part of the code base (although perhaps not that likely for logging).