Reputation: 172606
This question is related to Steven’s answer - here. He proposed a very good logger wrapper. I will paste his code below:
public interface ILogger
{
void Log(LogEntry entry);
}
public static class LoggerExtensions
{
public static void Log(this ILogger logger, string message)
{
logger.Log(new LogEntry(LoggingEventType.Information,
message, null));
}
public static void Log(this ILogger logger, Exception exception)
{
logger.Log(new LogEntry(LoggingEventType.Error,
exception.Message, exception));
}
// More methods here.
}
So, my question is what is the proper way to create implementation that proxies to Microsoft.Extensions.Logging and what is the best way to use it later in the code?
Note: this question is a copy of this question about log4net but now specific to Microsoft.Extensions.Logging.
Upvotes: 7
Views: 13416
Reputation: 27842
Here is my solution. Not too unlike Steven's. But not exactly like it. And mine leans toward dotNetCore, but the same thing can be accomplished in dotnetFW.
DotNetCoreLogger is the concrete of "MY" ILogger. And I inject the "microsoft" ILogger (Microsoft.Extensions.Logging.ILogger) into "My" concrete logger.
There is another SOF answer that "inspired" "my" logging abstraction....and after going from DotNetFramework (classic) to DotNotCore, I am so glad I did a "my" ILogger abstraction.
using System;
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public interface ILogger
{
void Log(LogEntry entry);
void Log(string message);
void Log(Exception exception);
}
}
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public enum LoggingEventTypeEnum
{
Debug,
Information,
Warning,
Error,
Fatal
};
}
using System;
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public class LogEntry
{
public readonly LoggingEventTypeEnum Severity;
public readonly string Message;
public readonly Exception Exception;
public LogEntry(LoggingEventTypeEnum severity, string message, Exception exception = 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;
}
}
}
using System;
using Microsoft.Extensions.Logging;
namespace MyApplication.Infrastructure.Logging.LoggingCoreConcrete
{
public class DotNetCoreLogger<T> : MyApplication.Infrastructure.Logging.LoggingAbstractBase.ILogger
{
private readonly ILogger<T> concreteLogger;
public DotNetCoreLogger(Microsoft.Extensions.Logging.ILogger<T> concreteLgr)
{
this.concreteLogger = concreteLgr ?? throw new ArgumentNullException("Microsoft.Extensions.Logging.ILogger is null");
}
public void Log(MyApplication.Infrastructure.Logging.LoggingAbstractBase.LogEntry entry)
{
if (null == entry)
{
throw new ArgumentNullException("LogEntry is null");
}
else
{
switch (entry.Severity)
{
case LoggingAbstractBase.LoggingEventTypeEnum.Debug:
this.concreteLogger.LogDebug(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Information:
this.concreteLogger.LogInformation(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Warning:
this.concreteLogger.LogWarning(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Error:
this.concreteLogger.LogError(entry.Message, entry.Exception);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Fatal:
this.concreteLogger.LogCritical(entry.Message, entry.Exception);
break;
default:
throw new ArgumentOutOfRangeException(string.Format("LogEntry.Severity out of range. (Severity='{0}')", entry.Severity));
}
}
}
public void Log(string message)
{
this.concreteLogger.LogInformation(message);
}
public void Log(Exception exception)
{
/* "Always pass exception as first parameter" from https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/ */
/* there is an issue with https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/LoggerExtensions.cs
* the default MessageFormatter (for the extension methods) is not doing anything with the "error". this plays out as not getting full exception information when using extension methods. :(
*
* private static string MessageFormatter(FormattedLogValues state, Exception error)
* {
* return state.ToString();
* }
*
* Below code/implementation is purposely NOT using any extension method(s) to bypass the above MessageFormatter mishap.
*
* */
this.concreteLogger.Log(LogLevel.Error, exception, exception.Message);
}
}
}
/* IoC/DI below */
private static System.IServiceProvider BuildDi(Microsoft.Extensions.Configuration.IConfiguration config)
{
//setup our DI
IServiceProvider serviceProvider = new ServiceCollection()
.AddLogging()
.AddSingleton<IConfiguration>(config)
.AddSingleton<MyApplication.Infrastructure.Logging.LoggingAbstractBase.ILogger, MyApplication.Infrastructure.Logging.LoggingCoreConcrete.DotNetCoreLogger<Program>>()
.BuildServiceProvider();
//configure console logging
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug);
return serviceProvider;
}
Upvotes: 1
Reputation: 172606
So, my question is what is the proper way to create implementation that proxies to Microsoft.Extensions.ILogger?
you should create something like:
public sealed class MicrosoftLoggingAdapter : ILogger
{
private readonly Microsoft.Extensions.ILogger adaptee;
public MicrosoftLoggingAdapter (Microsoft.Extensions.ILogger adaptee) =>
this.adaptee = adaptee;
public void Log(LogEntry e) =>
adaptee.Log(ToLevel(e.Severity), 0, e.Message, e.Exception, (s, _) => s);
private static LogLevel ToLevel(LoggingEventType s) =>
s == LoggingEventType.Debug ? LogLevel.Debug :
s == LoggingEventType.Information ? LogLevel.Information :
s == LoggingEventType.Warning ? LogLevel.Warning :
s == LoggingEventType.Error ? LogLevel.Error :
LogLevel.Critical;
}
what is the best way to use it later in the code?
If you are using a DI container, then just use the DI container to map ILogger
to MicrosoftLoggingAdapter
. You also need to register Microsoft.Extensions.ILogger
, or just give an instance of MS logger to the DI container to inject it to the MicrosoftLoggingAdapter constructor.
If you don't use a DI container, i.e., you use Pure DI, then you do something like this:
var logger = loggerFactory.CreateLogger("Application");
ILogger logging_adapter = new MicrosoftLoggingAdapter(logger);
var myobject = new MyClass(other_dependencies_here, logging_adapter);
Upvotes: 10