Reputation: 6363
Having read Steven's answer here, it got me thinking about switching out logging systems and what a PITA that can be. The simplicity of the interface is what I like the most, and it's raw, no other project to bring in, nothing, except the code you write to produce the events. I have modified the original code so that I can pass in the class name in the context parameter of the LogEntry:
public interface ILogger
{
void Log(LogEntry entry);
}
public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };
public class LogEntry
{
public readonly LoggingEventType Severity;
public readonly string Message;
public readonly Exception Exception;
public readonly Type Context;
public LogEntry(LoggingEventType severity,
string message,
Exception exception = null,
Type context = null)
{
if (message == null) throw new ArgumentNullException("message");
if (message == string.Empty) throw new ArgumentException("empty", "message");
this.Severity = severity;
this.Message = message;
this.Exception = exception;
this.Context = context;
}
}
Question #1: Does anything seem wrong about passing in the Type/context param?
This post also shed some light on writing an adapter based on Log4net, and is the basis of my NLog adapter, although I don't use constructor injection of an ILogger.
class NLogAdapter : ILogger
{
public void Log(LogEntry entry)
{
NLog.Logger log;
if (entry.Context != null)
{
log = NLog.LogManager.GetLogger(entry.Context.GetType().Namespace);
}
else
{
log = NLog.LogManager.GetLogger("DefaultLogger");
}
switch (entry.Severity)
{
case LoggingEventType.Debug:
log.Debug(entry.Exception, entry.Message);
break;
case LoggingEventType.Information:
log.Info(entry.Exception, entry.Message);
break;
case LoggingEventType.Warning:
log.Warn(entry.Exception, entry.Message);
break;
case LoggingEventType.Error:
log.Error(entry.Exception, entry.Message);
break;
case LoggingEventType.Fatal:
log.Fatal(entry.Exception, entry.Message);
break;
default:
throw new ArgumentOutOfRangeException(nameof(entry));
}
}
}
Question #2: I'm a bit unsure about the use of the log manager for every call, is this the most correct way to get an instance of a NLog Logger? Are there any other recommendations that you might give me?
Note: This Adapter may be a singleton in a DI container, or it could be used/made into a static class.
Thank you, Stephen
Upvotes: 0
Views: 1658
Reputation: 172865
I'm a bit unsure about the use of the log manager for every call, is this the most correct way to get an instance of a NLog Logger? Are there any other recommendations that you might give me?
A more typical design (and performant) design is one where you create a generic implementation, and inject a closed-generic singleton implementation with its generic argument being equal to the class it gets injected into, as can be seen in the following answer:
class NLogAdapter<T> : ILogger
{
private static readonly NLog.Logger log =
NLog.LogManager.GetLogger(typeof(T).FullName);
}
This not only prevents you from having to resolve a NLog logger on each call, it prevents you from having to pass along the context to the LogEntry.
Injecting it into consumers, would look like this:
new ProductController(new NLogAdapter<ProductController>())
new HomeController(new NLogAdapter<HomeController>())
If you're using a DI Container, it depends on the container you're using, how this must be configured. With Simple Injector for instance, it's a matter of making a contextual registration as follows:
container.RegisterConditional(typeof(ILogger),
c => typeof(NLogAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
c => true);
When the logging adapter is a singleton, it means the overhead is reduced to the minimum.
Question #1: Does anything seem wrong about passing in the Type/context param?
When making your logger-implementation contextual, there is no need to pass on contextual information like that during logging.
Big warning: do not make the ILogger
abstraction generic (as Microsoft did in their logging library), that would just complicate things for the consumers and their tests. That would be a severe design flaw.
Upvotes: 2