Moving NLog to it's Own Logger Service Project and Keep Current Logger Syntax

Viewed 37

I have a .Net 6 Web API project with NLog configured following the steps at Getting started with ASP.NET Core 6. I'd like to move NLog references to it's own Class Library. I was following an article on ASP.NET Core Web API – Logging With NLog. The problem is that this method requires me to change all my controllers from

private readonly ILogger<MyController> _logger;
public MyController(ILogger<MyController> logger) {...}

to

private readonly ILoggerManager _logger;
public MyController(ILoggerManager logger) {...}

Is there a way to get NLog in it's own Class Library, yet keep the current syntax that was built-in to the API?

Here's my current program.cs file:

using NLog;

var logger = LogManager
            .LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/Configs/NLog/nlog.config"))
            .Setup()
            .SetupExtensions(x => x.RegisterLayoutRenderer<BuildConfigLayoutRenderer>("BuildConfiguration"))
            .GetCurrentClassLogger();
logger.Debug("init main");

try {
    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.

    // NLog: Setup NLog for Dependency injection

    /********************************************
    the 2 lines below are:
        1) the default taken from NLog docs and
        2) the code from the article I was following (currently disabled)
    ********************************************/

    builder.Host.UseNLog(); // 1
    //builder.ConfigureLoggerService(); // 2

    /********************************************/

    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    var app = builder.Build();

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment()) {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseHttpsRedirection();

    app.UseAuthorization();

    app.MapControllers();

    app.Run();
} catch (Exception exception) {
    logger.Error(exception, "Stopped program because of exception");
    throw;
} finally {
    LogManager.Shutdown();
}
1 Answers

Following the Microsoft article Implement a custom logging provider in .NET, I came up with the following implementation.

Created new project Created 4 files exactly as listed in the article

LoggerServiceConfiguration.cs

public class LoggerServiceConfiguration {
    public int EventId { get; set; }

    public Dictionary<LogLevel, ConsoleColor> LogLevels { get; set; } = new() {
        [LogLevel.Information] = ConsoleColor.Green,
        [LogLevel.Critical] = ConsoleColor.DarkRed
    };
}

LoggerServiceProvider.cs

[UnsupportedOSPlatform("browser")]
[ProviderAlias("LoggerService")]
public sealed class LoggerServiceProvider : ILoggerProvider {
    private readonly IDisposable _onChangeToken;
    private LoggerServiceConfiguration _currentConfig;
    private readonly ConcurrentDictionary<string, LoggerService> _loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public LoggerServiceProvider(IOptionsMonitor<LoggerServiceConfiguration> config) {
        _currentConfig = config.CurrentValue;
        _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, name => new LoggerService(name, GetCurrentConfig));

    private LoggerServiceConfiguration GetCurrentConfig() => _currentConfig;

    public void Dispose() {
        _loggers.Clear();
        _onChangeToken.Dispose();
    }
}

LoggerService.cs

I did modify the Log method here

public sealed class LoggerService : Microsoft.Extensions.Logging.ILogger {
    private readonly string _name;
    private readonly Func<LoggerServiceConfiguration> _getCurrentConfig;

    private static NLog.ILogger _logger = LogManager.GetCurrentClassLogger();

    public LoggerService(string name, Func<LoggerServiceConfiguration> getCurrentConfig) => (_name, _getCurrentConfig) = (name, getCurrentConfig);

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) => _getCurrentConfig().LogLevels.ContainsKey(logLevel);

    public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) {
        switch (logLevel) {
            case Microsoft.Extensions.Logging.LogLevel.Trace:
                _logger.Trace($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.Debug:
                _logger.Debug($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.Information:
                _logger.Info($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.Warning:
                _logger.Warn($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.Error:
                _logger.Error($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.Critical:
                _logger.Fatal($"{formatter(state, exception)}");
                break;
            case Microsoft.Extensions.Logging.LogLevel.None:
                _logger.Trace($"{formatter(state, exception)}");
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);
        }
    }
}

program.cs

using NLog;

var logger = LogManager
            .LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/Configs/NLog/nlog.config"))
            .Setup()
            .SetupExtensions(x => x.RegisterLayoutRenderer<BuildConfigLayoutRenderer>("BuildConfiguration"))
            .GetCurrentClassLogger();
logger.Debug("init main");

try {
    var builder = WebApplication.CreateBuilder(args);

    // Add services to the container.

    // NLog: Setup NLog for Dependency injection

    /********************************************/
    // Call our custom logger
    builder.Logging.AddLoggerService();
    /********************************************/

    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();

    var app = builder.Build();

    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment()) {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseHttpsRedirection();

    app.UseAuthorization();

    app.MapControllers();

    app.Run();
} catch (Exception exception) {
    logger.Error(exception, "Stopped program because of exception");
    throw;
} finally {
    LogManager.Shutdown();
}

I'm not sure if this is correctly done but it works. Not how I like it since I don't know the best way to log which controller and action was called so I'll put a hold on it for now until I research more.

Related