Implementing Microsoft.Extensions.Logging.ILogger with NLog

Thu, 25 Jun 2020 12:41 UTC by garethbrown

Why

I've designed this ILogger implementation to solve an issue I encountered today where the code I was working was wasn't logging errors when the error occurred in a background thread. It was failing silently because unless you configure NLog specifically to throw exceptions, it won't.

The principle issue in my case was that the usual ILogger<T> instance instantiated in the class constructor wasn't available because the controller object on the main thread had disposed before the thread had completed. Fair enough. I then tried creating an instance of ILogger<T> via a service locator within the thread. And that failed silently too (I'm not sure of what was going on internally here but I'm wondering if it's to do with the Logger being typed to the class which is disposed, would need to check the framework source code here).

So I opted to create a class that implements Microsoft.Extensions.Logging.ILogger, and register it as a singleton that can be used everywhere and will not be disposed with the container object.

So we loose the capabilities of ILogger

https://stackoverflow.com/questions/51345161/should-i-take-ilogger-iloggert-iloggerfactory-or-iloggerprovider-for-a-libra
https://nblumhardt.com/2016/11/ilogger-beginscope/

The code

Package references that may be required depending on the .NET core framework version you are targeting

<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.5" />
<PackageReference Include="NLog" Version="4.5.10" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.7.0" />

The NLogLogger class it's self

using Microsoft.Extensions.Logging;
using NLog;
using System;

namespace ANamespace
{
    public class NLogLogger : Microsoft.Extensions.Logging.ILogger
    {
        private NLog.ILogger NLogLoggerInstance { get; }

        private class DisposableStub : IDisposable
        {
            public void Dispose()
            {
                // Do nothing
            }
        }

        public NLogLogger()
        {
            NLogLoggerInstance = LogManager.LoadConfiguration("NLog.config").GetCurrentClassLogger();
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            // Not implementing this feature

            return new DisposableStub();
        }

        public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
        {
            if (logLevel == Microsoft.Extensions.Logging.LogLevel.Trace)
            {
                return NLogLoggerInstance.IsTraceEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Debug)
            {
                return NLogLoggerInstance.IsDebugEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Information)
            {
                return NLogLoggerInstance.IsInfoEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Warning)
            {
                return NLogLoggerInstance.IsWarnEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Error)
            {
                return NLogLoggerInstance.IsErrorEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Critical)
            {
                return NLogLoggerInstance.IsFatalEnabled;
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.None)
            {
                return false;
            }
            else
            {
                throw new InvalidOperationException($"Unhandled type of {nameof(Microsoft.Extensions.Logging.LogLevel)}");
            }
        }

        public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (logLevel == Microsoft.Extensions.Logging.LogLevel.Trace)
            {
                NLogLoggerInstance.Trace(formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Debug)
            {
                NLogLoggerInstance.Debug(formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Information)
            {
                NLogLoggerInstance.Info(exception, formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Warning)
            {
                NLogLoggerInstance.Warn(exception, formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Error)
            {
                NLogLoggerInstance.Error(exception, formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.Critical)
            {
                NLogLoggerInstance.Fatal(exception, formatter(state, exception));
            }
            else if (logLevel == Microsoft.Extensions.Logging.LogLevel.None)
            {
                NLogLoggerInstance.Info(formatter(state, exception));
            }
        }
    }
}

Registering as a service for dependency injection in your ASP.NET core app:

services.AddSingleton<Microsoft.Extensions.Logging.ILogger>(new ANamespace.NLogLogger());

The information on this site is provided “AS IS” and without warranties of any kind either
express or implied. To the fullest extent permissible pursuant to applicable laws, the author disclaims all warranties, express or implied, including, but not limited to, implied warranties of merchantability, non-infringement and suitability for a particular purpose.

UI block loader
One moment please ...