Oshini Gunarwardena
Oshini Gunarwardena

Reputation: 41

How to add two logger files in .net core using LoggerFactory

public class Startup
{
    public IConfiguration Configuration { get; } 

    public void ConfigureServices(IServiceCollection services)
    {   
        loggerFactory.AddFile(logFilePath1);
        services.AddSingleton<ILoggerFactory>(loggerFactory);

        loggerFactory.AddFile(logFilePath2);
        services.AddSingleton<ILoggerFactory>(loggerFactory);
    }
}

With in the startup.cs class, I create two loggers . Since it has two loggers how can I set the Ilogger data in the controller? can it do using normal way? Or is there any different way to pass logger filename when logged within the controller?

Upvotes: 3

Views: 6755

Answers (3)

Henrik H&#248;yer
Henrik H&#248;yer

Reputation: 1662

Here is a updated version of the AggregateLogger class from from BeVenon's answer:

public class AggregateLogger : ILogger
{
    private ILogger[] Loggers { get; }

    public AggregateLogger(params ILogger[] loggers) 
    {
        Loggers = loggers;
    }

    public IDisposable BeginScope<TState>(TState state) where TState : notnull
    {
        var disposables = new List<IDisposable>();

        foreach (var log in Loggers)
        {
            var scope = log.BeginScope(state);
            if (scope != null) disposables.Add(scope);
        }

        var disposer = new Disposer(disposables);
        return disposer;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        foreach (var log in Loggers) 
            log.Log(logLevel, eventId, state, exception, formatter);
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return Loggers.Any(o => o.IsEnabled(logLevel));
    }
}

public class Disposer : IDisposable
{
    readonly List<IDisposable> _disposables;

    public Disposer(List<IDisposable> disposables) 
    {
        _disposables = disposables;
    }

    public void Dispose()
    {
        foreach (var obj in _disposables)
            obj.Dispose();
    }
}

Upvotes: 0

BVernon
BVernon

Reputation: 3747

I created an aggregate logger for this purpose.

public class AggregateLogger : ILogger
{
    ILogger[] Loggers { get; set; }

    public AggregateLogger(params ILogger[] loggers) 
    {
        Loggers = loggers;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        List<IDisposable> disposables = new List<IDisposable>();
        
        foreach(var log in Loggers) 
            disposables.Add(log.BeginScope(state));

        Disposer disposer = new Disposer(disposables);
        return disposer;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return Loggers.Any(o => o.IsEnabled(logLevel));
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        foreach(var log in Loggers)
            log.Log(logLevel, eventId, state, exception, formatter);
    }
}

public class Disposer : IDisposable
{
    List<IDisposable> _disposables;

    public Disposer(List<IDisposable> disposables) 
    {
        _disposables = disposables;
    }

    public void Dispose()
    {
        foreach (var obj in _disposables)
            obj.Dispose();
    }
}

Upvotes: 2

Martin Liversage
Martin Liversage

Reputation: 106816

OK, so you want to have two different loggers in a single controller and you want these two loggers to log to different files. The .NET Core logging does not have good support for this scenario so it requires a bit of hacking to achieve this. Whenever I find myself in a situation where I get a a lot of resistance from the framework I'm using I reconsider if what I'm trying to do is good idea and if it is whether I should use another framework so you might want to do the same. With that in mind here is a way to achieve what you want.

Loggers can be identified by a category. In your case you want a single controller to have two different loggers so you have to use ILoggerFactory to create the loggers (you could use the generic ILogger<T> interface but it becomes a bit weird because you need two different types for T):

public class MyController : Controller
{
    private readonly ILogger logger1;
    private readonly ILogger logger2;

    public Controller1(ILoggerFactor loggerFactory)
    {
        logger1 = loggerFactory.Create("Logger1");
        logger2 = loggerFactory.Create("Logger2");
    }
}

The categories of the loggers are Logger1 and Logger2.

Each logger will by default log to all the configured providers. You want a logger with one category to log to one provider and a logger with another category to log to another provider.

While you can create filters that are based on category, provider and log level the problem is that you want to use the same provider for both categories. Providers are identified by their type so you cannot create a rule that targets a specific instance of a provider. If you create a rule for the file provider it will affect all configured file providers.

So this is where the hacking starts: You have to create your own provider types that are linked to the files to be able to filter on each file.

.NET Core does not have support for logging to files so you need a third party provider. You have not specified which provider you use so for this example I will use the Serilog file sink together with the Serilog provider that allows you to plug a Serilog logger into the .NET Core logging framework.

To be able to filter on provider you have to create your own provider. Luckily, that is easily done by deriving from the SerilogLoggerProvider:

class SerilogLoggerProvider1 : SerilogLoggerProvider
{
    public SerilogLoggerProvider1(Serilog.ILogger logger) : base(logger) { }
}

class SerilogLoggerProvider2 : SerilogLoggerProvider
{
    public SerilogLoggerProvider2(Serilog.ILogger logger) : base(logger) { }
}

These two providers does not add any functionality but allows you to create filter that targets a specific provider.

Next step is crating two different Serilog loggers that log to different files:

var loggerConfiguration1 = new LoggerConfiguration()
    .WriteTo.File("...\1.log");
var loggerConfiguration2 = new LoggerConfiguration()
    .WriteTo.File("...\2.log");
var logger1 = loggerConfiguration1.CreateLogger();
var logger2 = loggerConfiguration2.CreateLogger();

You configure your logging in Main by calling the extension method .ConfigureLogging:

.ConfigureLogging((hostingContext, loggingBuilder) =>
    {
        loggingBuilder
            .AddProvider(new SerilogLoggerProvider1(logger1))
            .AddFilter("Logger1", LogLevel.None)
            .AddFilter<SerilogLoggerProvider1>("Logger1", LogLevel.Information)
            .AddProvider(new SerilogLoggerProvider2(logger2))
            .AddFilter("Logger2", LogLevel.None)
            .AddFilter<SerilogLoggerProvider2>("Logger2", LogLevel.Information);
    })

Each provider (which is associated with a specific file) are added and then two filters are configured for each provider. I find the filter evaluation rules hard to reason about but the two filters added - one with LogLevel.None and another with LogLevel.Information - actually achieves the desired result of ensuring that log messages for the two different categories are routed correctly to the two different providers. If a third provider is added it will not be affected by these filters and messages from both categories will be logged by the third provider.

Upvotes: 4

Related Questions