I reckon it is good practice to introduce abstraction layer on top of a widely used third-party components such as logging. In fact this is a realization of letter "D" of SOLID principles. Depend upon Abstractions. Do not depend upon concretions - it says. And following this principle will ensure you that switching between third-party or our own components will be fast and painless.
Lets consider abstraction for my favorite .NET logging library NLog. First implementation that comes to mind is:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface ILogger | |
{ | |
void Info(string message); | |
void Info(string message, Exception exc); | |
void Debug(string message); | |
void Debug(string message, Exception exc); | |
void Warn(string message); | |
void Warn(string message, Exception exc); | |
void Error(string message); | |
void Error(string message, Exception exc); | |
void Fatal(string message); | |
void Fatal(string message, Exception exc); | |
void Trace(string message); | |
void Trace(string message, Exception exc); | |
} |
This abstraction gives possibility to create log messages with different priority and add exception information.
However if you implement this interface using NLog one important logger property will remain uninitialized - Name. This property is vital for identifying loggers and is often used for routing log messages and getting location in code where log message was created. While usually logger is initialized by calling LogManager.GetCurrentClassLogger() and this makes logger to have its Name referencing to a class where logger instance was initialized, there will be no use of this method when it is called inside of implementation of ILogger - all loggers will have same name.
The most straightforward approach is to add logger name parameter to abstraction and set it every time when call to logger is made. This requires extra coding and introduces complexity to your code. Not the best variant for utility services like logger.
Another solution of this problem is to set name with dependency injection framework. This is a good example how it can be done with Ninject.
Third approach became available after release of .NET 4.5 when set of caller attributes has been introduced. It does not require lot of coding and use of third-party libraries - you simply add string parameter with CallerFilePath attribute defined to all methods of the interface and its realization and initialize logger using processed value of this parameter. Resulting abstraction will be:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface ILogger | |
{ | |
void Info(string message, [CallerFilePath] string sourceFilePath = null); | |
void Info(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
void Debug(string message, [CallerFilePath] string sourceFilePath = null); | |
void Debug(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
void Warn(string message, [CallerFilePath] string sourceFilePath = null); | |
void Warn(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
void Error(string message, [CallerFilePath] string sourceFilePath = null); | |
void Error(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
void Fatal(string message, [CallerFilePath] string sourceFilePath = null); | |
void Fatal(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
void Trace(string message, [CallerFilePath] string sourceFilePath = null); | |
void Trace(string message, Exception exc, [CallerFilePath] string sourceFilePath = null); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Logger : ILogger | |
{ | |
private static NLog.Logger GetInnerLogger(string sourceFilePath) | |
{ | |
var logger = sourceFilePath == null ? LogManager.GetCurrentClassLogger() : LogManager.GetLogger(Path.GetFileName(sourceFilePath)); | |
return logger; | |
} | |
public void Info(string message, [CallerFilePath] string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Info(message); | |
} | |
public void Info(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).InfoException(message, exc); | |
} | |
public void Debug(string message, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Debug(message); | |
} | |
public void Debug(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).DebugException(message,exc); | |
} | |
public void Warn(string message, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Warn(message); | |
} | |
public void Warn(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).WarnException(message, exc); | |
} | |
public void Error(string message, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Error(message); | |
} | |
public void Error(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).ErrorException(message, exc); | |
} | |
public void Fatal(string message, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Fatal(message); | |
} | |
public void Fatal(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).FatalException(message, exc); | |
} | |
public void Trace(string message, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).Trace(message); | |
} | |
public void Trace(string message, Exception exc, [CallerFilePath]string sourceFilePath = null) | |
{ | |
GetInnerLogger(sourceFilePath).TraceException(message, exc); | |
} | |
} |
No comments:
Post a Comment